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

* core,eth: add empty tx logger hooks

* core,eth: add initial and remaining gas to tx hooks

* store tx gasLimit in js tracer

* use gasLimit to compute intrinsic cost for js tracer

* re-use rules in transitiondb

* rm logs

* rm logs

* Mv some fields from Start to TxStart

* simplify sender lookup in prestate tracer

* mv env to TxStart

* Revert "mv env to TxStart"

This reverts commit 656939634b9aff19f55a1cd167345faf8b1ec310.

* Revert "simplify sender lookup in prestate tracer"

This reverts commit ab65bce48007cab99e68232e7aac2fe008338d50.

* Revert "Mv some fields from Start to TxStart"

This reverts commit aa50d3d9b2559addc80df966111ef5fb5d0c1b6b.

* fix intrinsic gas for prestate tracer

* add comments

* refactor

* fix test case

* simplify consumedGas calc in prestate tracer
This commit is contained in:
Sina Mahmoodi 2022-03-31 11:51:44 +02:00 committed by GitHub
parent da16d089c0
commit 3fd16af5a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 83 additions and 40 deletions

@ -287,15 +287,23 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if err := st.preCheck(); err != nil { if err := st.preCheck(); err != nil {
return nil, err return nil, err
} }
msg := st.msg
sender := vm.AccountRef(msg.From()) if st.evm.Config.Debug {
homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) st.evm.Config.Tracer.CaptureTxStart(st.initialGas)
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber) defer func() {
london := st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) st.evm.Config.Tracer.CaptureTxEnd(st.gas)
contractCreation := msg.To() == nil }()
}
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 // 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 { if err != nil {
return nil, err return nil, err
} }
@ -310,7 +318,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} }
// Set up the initial access list. // 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()) st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
} }
var ( var (
@ -325,7 +333,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) 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 // Before EIP-3529: refunds were capped to gasUsed / 2
st.refundGas(params.RefundQuotient) st.refundGas(params.RefundQuotient)
} else { } else {
@ -333,7 +341,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
st.refundGas(params.RefundQuotientEIP3529) st.refundGas(params.RefundQuotientEIP3529)
} }
effectiveTip := st.gasPrice effectiveTip := st.gasPrice
if london { if rules.IsLondon {
effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee)) 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)) st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip))

@ -29,10 +29,16 @@ import (
// Note that reference types are actual VM data structures; make copies // Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call. // if you need to retain them beyond the current call.
type EVMLogger interface { 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) 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) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
CaptureExit(output []byte, gasUsed uint64, err error) 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) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
} }

@ -30,7 +30,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "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/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
tracers2 "github.com/ethereum/go-ethereum/eth/tracers" tracers2 "github.com/ethereum/go-ethereum/eth/tracers"
@ -419,6 +418,7 @@ type jsTracer struct {
activePrecompiles []common.Address // Updated on CaptureStart based on given rules activePrecompiles []common.Address // Updated on CaptureStart based on given rules
traceSteps bool // When true, will invoke step() on each opcode traceSteps bool // When true, will invoke step() on each opcode
traceCallFrames bool // When true, will invoke enter() and exit() js funcs 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, // New instantiates a new tracer instance. code specifies a Javascript snippet,
@ -679,7 +679,18 @@ func wrapError(context string, err error) error {
return fmt.Errorf("%v in server-side tracer function '%v'", err, context) 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) { 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.env = env
jst.ctx["type"] = "CALL" jst.ctx["type"] = "CALL"
@ -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) rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
jst.activePrecompiles = vm.ActivePrecompiles(rules) jst.activePrecompiles = vm.ActivePrecompiles(rules)
// Compute intrinsic gas // Intrinsic costs are the only things reduced from initial gas to this point
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) jst.ctx["intrinsicGas"] = jst.gasLimit - gas
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
} }
// CaptureState implements the Tracer interface to trace a single step of VM execution. // CaptureState implements the Tracer interface to trace a single step of VM execution.
@ -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) { func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
jst.ctx["output"] = output jst.ctx["output"] = output
jst.ctx["time"] = t.String() jst.ctx["time"] = t.String()

@ -62,15 +62,19 @@ func testCtx() *vmContext {
func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) { func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) {
var ( var (
env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer}) env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer})
gasLimit uint64 = 31000
startGas uint64 = 10000 startGas uint64 = 10000
value = big.NewInt(0) value = big.NewInt(0)
contract = vm.NewContract(account{}, account{}, value, startGas) contract = vm.NewContract(account{}, account{}, value, startGas)
) )
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} 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) tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
ret, err := env.Interpreter().Run(contract, []byte{}, false) ret, err := env.Interpreter().Run(contract, []byte{}, false)
tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err) tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
// Rest gas assumes no refund
tracer.CaptureTxEnd(startGas - contract.Gas)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -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) 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. // AccessList returns the current accesslist maintained by the tracer.
func (a *AccessListTracer) AccessList() types.AccessList { func (a *AccessListTracer) AccessList() types.AccessList {
return a.list.accessList() return a.list.accessList()

@ -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 (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. // StructLogs returns the captured log entries.
func (l *StructLogger) StructLogs() []StructLog { return l.logs } func (l *StructLogger) StructLogs() []StructLog { return l.logs }
@ -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 (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
func (*mdLogger) CaptureTxStart(gasLimit uint64) {}
func (*mdLogger) CaptureTxEnd(restGas uint64) {}

@ -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) CaptureExit(output []byte, gasUsed uint64, err error) {}
func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {}
func (l *JSONLogger) CaptureTxEnd(restGas uint64) {}

@ -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 (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 // GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`). // error arising from the encoding or forceful termination (via `Stop`).
func (t *fourByteTracer) GetResult() (json.RawMessage, error) { func (t *fourByteTracer) GetResult() (json.RawMessage, error) {

@ -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) 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 // GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`). // error arising from the encoding or forceful termination (via `Stop`).
func (t *callTracer) GetResult() (json.RawMessage, error) { func (t *callTracer) GetResult() (json.RawMessage, error) {

@ -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 (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. // GetResult returns an empty json object.
func (t *noopTracer) GetResult() (json.RawMessage, error) { func (t *noopTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(`{}`), nil return json.RawMessage(`{}`), nil

@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "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/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
@ -47,6 +46,7 @@ type prestateTracer struct {
prestate prestate prestate prestate
create bool create bool
to common.Address to common.Address
gasLimit uint64 // Amount of gas bought for the whole tx
interrupt uint32 // Atomic flag to signal execution interruption interrupt uint32 // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
} }
@ -63,14 +63,6 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
t.create = create t.create = create
t.to = to 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(from)
t.lookupAccount(to) t.lookupAccount(to)
@ -79,17 +71,11 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
toBal = new(big.Int).Sub(toBal, value) toBal = new(big.Int).Sub(toBal, value)
t.prestate[to].Balance = hexutil.EncodeBig(toBal) 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. // We need to re-add them to get the pre-tx balance.
fromBal := hexutil.MustDecodeBig(t.prestate[from].Balance) fromBal := hexutil.MustDecodeBig(t.prestate[from].Balance)
gasPrice := env.TxContext.GasPrice gasPrice := env.TxContext.GasPrice
consumedGas := new(big.Int).Mul( consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
gasPrice,
new(big.Int).Add(
new(big.Int).SetUint64(intrinsicGas),
new(big.Int).SetUint64(gas),
),
)
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas)) fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
t.prestate[from].Balance = hexutil.EncodeBig(fromBal) t.prestate[from].Balance = hexutil.EncodeBig(fromBal)
t.prestate[from].Nonce-- t.prestate[from].Nonce--
@ -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) 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 // GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`). // error arising from the encoding or forceful termination (via `Stop`).
func (t *prestateTracer) GetResult() (json.RawMessage, error) { func (t *prestateTracer) GetResult() (json.RawMessage, error) {