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:
parent
da16d089c0
commit
3fd16af5a9
|
@ -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
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
if !london {
|
||||
if !rules.IsLondon {
|
||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
||||
st.refundGas(params.RefundQuotient)
|
||||
} else {
|
||||
|
@ -333,7 +341,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
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))
|
||||
|
|
|
@ -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)
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
@ -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) {}
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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--
|
||||
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue