Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core,eth: implement tx-level hooks for tracers #24510

Merged
merged 18 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,23 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if err := st.preCheck(); err != nil {
return nil, err
}
msg := st.msg
sender := vm.AccountRef(msg.From())
homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber)
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber)
london := st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber)
contractCreation := msg.To() == nil

if st.evm.Config.Debug {
st.evm.Config.Tracer.CaptureTxStart(st.initialGas)
defer func() {
st.evm.Config.Tracer.CaptureTxEnd(st.gas)
}()
}

var (
msg = st.msg
sender = vm.AccountRef(msg.From())
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil)
contractCreation = msg.To() == nil
)

// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul)
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul)
if err != nil {
return nil, err
}
Expand All @@ -310,7 +318,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}

// Set up the initial access list.
if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil); rules.IsBerlin {
if rules.IsBerlin {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
}
var (
Expand All @@ -325,15 +333,15 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
}

if !london {
if !rules.IsLondon {
// Before EIP-3529: refunds were capped to gasUsed / 2
st.refundGas(params.RefundQuotient)
} else {
// After EIP-3529: refunds are capped to gasUsed / 5
st.refundGas(params.RefundQuotientEIP3529)
}
effectiveTip := st.gasPrice
if london {
if rules.IsLondon {
effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee))
}
st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip))
Expand Down
10 changes: 8 additions & 2 deletions core/vm/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ import (
// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I guess one thing though. A tracer/logger which implements this, but not others -- wouldn't they also want to tx hash and index?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a look and the right now the only tracer taking this info is jsTracer and that through its constructor (which makes it a bit special).

Do you think it'll be useful to add this info to the logger interface? One advantage I can see it would make the js tracer a bit more standard.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I do think it would "make sense" to include it here and now: if we're adding tx level hooks, we should also provide the basic tx info. However, I now looked into it a bit more, and the hash/index is not information which we have readily available in state_transition.go at this point, so maybe it's more hassle than it's worth... ?

CaptureTxEnd(restGas uint64)
// Top call frame
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, 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)
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
}
27 changes: 16 additions & 11 deletions eth/tracers/js/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
tracers2 "github.com/ethereum/go-ethereum/eth/tracers"
Expand Down Expand Up @@ -419,6 +418,7 @@ type jsTracer struct {
activePrecompiles []common.Address // Updated on CaptureStart based on given rules
traceSteps bool // When true, will invoke step() on each opcode
traceCallFrames bool // When true, will invoke enter() and exit() js funcs
gasLimit uint64 // Amount of gas bought for the whole tx
}

// New instantiates a new tracer instance. code specifies a Javascript snippet,
Expand Down Expand Up @@ -679,7 +679,18 @@ func wrapError(context string, err error) error {
return fmt.Errorf("%v in server-side tracer function '%v'", err, context)
}

// CaptureStart implements the Tracer interface to initialize the tracing operation.
// CaptureTxStart implements the Tracer interface and is invoked at the beginning of
// transaction processing.
func (jst *jsTracer) CaptureTxStart(gasLimit uint64) {
jst.gasLimit = gasLimit
}

// CaptureTxStart implements the Tracer interface and is invoked at the end of
// transaction processing.
func (*jsTracer) CaptureTxEnd(restGas uint64) {}

// CaptureStart implements the Tracer interface and is invoked before executing the
// top-level call frame of a transaction.
func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
jst.env = env
jst.ctx["type"] = "CALL"
Expand All @@ -700,14 +711,8 @@ func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
jst.activePrecompiles = vm.ActivePrecompiles(rules)

// Compute intrinsic gas
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul)
if err != nil {
return
}
jst.ctx["intrinsicGas"] = intrinsicGas
// Intrinsic costs are the only things reduced from initial gas to this point
jst.ctx["intrinsicGas"] = jst.gasLimit - gas
}

// CaptureState implements the Tracer interface to trace a single step of VM execution.
Expand Down Expand Up @@ -760,7 +765,7 @@ func (jst *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, sco
}
}

// CaptureEnd is called after the call finishes to finalize the tracing.
// CaptureEnd is called after the top-level call finishes.
func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
jst.ctx["output"] = output
jst.ctx["time"] = t.String()
Expand Down
4 changes: 4 additions & 0 deletions eth/tracers/js/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,19 @@ func testCtx() *vmContext {
func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) {
var (
env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer})
gasLimit uint64 = 31000
startGas uint64 = 10000
value = big.NewInt(0)
contract = vm.NewContract(account{}, account{}, value, startGas)
)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}

tracer.CaptureTxStart(gasLimit)
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
ret, err := env.Interpreter().Run(contract, []byte{}, false)
tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
// Rest gas assumes no refund
tracer.CaptureTxEnd(startGas - contract.Gas)
if err != nil {
return nil, err
}
Expand Down
4 changes: 4 additions & 0 deletions eth/tracers/logger/access_list_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to com

func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}

func (*AccessListTracer) CaptureTxStart(gasLimit uint64) {}

func (*AccessListTracer) CaptureTxEnd(restGas uint64) {}

// AccessList returns the current accesslist maintained by the tracer.
func (a *AccessListTracer) AccessList() types.AccessList {
return a.list.accessList()
Expand Down
8 changes: 8 additions & 0 deletions eth/tracers/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to commo

func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}

func (*StructLogger) CaptureTxStart(gasLimit uint64) {}

func (*StructLogger) CaptureTxEnd(restGas uint64) {}

// StructLogs returns the captured log entries.
func (l *StructLogger) StructLogs() []StructLog { return l.logs }

Expand Down Expand Up @@ -347,3 +351,7 @@ func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad
}

func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}

func (*mdLogger) CaptureTxStart(gasLimit uint64) {}

func (*mdLogger) CaptureTxEnd(restGas uint64) {}
4 changes: 4 additions & 0 deletions eth/tracers/logger/logger_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,7 @@ func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.
}

func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}

func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {}

func (l *JSONLogger) CaptureTxEnd(restGas uint64) {}
4 changes: 4 additions & 0 deletions eth/tracers/native/4byte.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ func (t *fourByteTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64,
func (t *fourByteTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
}

func (*fourByteTracer) CaptureTxStart(gasLimit uint64) {}

func (*fourByteTracer) CaptureTxEnd(restGas uint64) {}

// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
Expand Down
4 changes: 4 additions & 0 deletions eth/tracers/native/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
}

func (*callTracer) CaptureTxStart(gasLimit uint64) {}

func (*callTracer) CaptureTxEnd(restGas uint64) {}

// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *callTracer) GetResult() (json.RawMessage, error) {
Expand Down
4 changes: 4 additions & 0 deletions eth/tracers/native/noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
}

func (*noopTracer) CaptureTxStart(gasLimit uint64) {}

func (*noopTracer) CaptureTxEnd(restGas uint64) {}

// GetResult returns an empty json object.
func (t *noopTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(`{}`), nil
Expand Down
26 changes: 9 additions & 17 deletions eth/tracers/native/prestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers"
Expand All @@ -47,6 +46,7 @@ type prestateTracer struct {
prestate prestate
create bool
to common.Address
gasLimit uint64 // Amount of gas bought for the whole tx
interrupt uint32 // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
}
Expand All @@ -63,14 +63,6 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
t.create = create
t.to = to

// Compute intrinsic gas
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
intrinsicGas, err := core.IntrinsicGas(input, nil, create, isHomestead, isIstanbul)
if err != nil {
return
}

t.lookupAccount(from)
t.lookupAccount(to)

Expand All @@ -79,17 +71,11 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
toBal = new(big.Int).Sub(toBal, value)
t.prestate[to].Balance = hexutil.EncodeBig(toBal)

// The sender balance is after reducing: value, gasLimit, intrinsicGas.
// The sender balance is after reducing: value and gasLimit.
// We need to re-add them to get the pre-tx balance.
fromBal := hexutil.MustDecodeBig(t.prestate[from].Balance)
gasPrice := env.TxContext.GasPrice
consumedGas := new(big.Int).Mul(
gasPrice,
new(big.Int).Add(
new(big.Int).SetUint64(intrinsicGas),
new(big.Int).SetUint64(gas),
),
)
consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
t.prestate[from].Balance = hexutil.EncodeBig(fromBal)
t.prestate[from].Nonce--
Expand Down Expand Up @@ -145,6 +131,12 @@ func (t *prestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to com
func (t *prestateTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
}

func (t *prestateTracer) CaptureTxStart(gasLimit uint64) {
t.gasLimit = gasLimit
}

func (t *prestateTracer) CaptureTxEnd(restGas uint64) {}

// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *prestateTracer) GetResult() (json.RawMessage, error) {
Expand Down