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: call frame tracing #23087

Merged
merged 48 commits into from Sep 17, 2021
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
00a42ee
core,eth: call frame tracing prototype
s1na Jun 22, 2021
d746339
core/vm: add create2 frame type
s1na Jun 23, 2021
5a253be
eth/tracers: use helper fn for pushing fields to js object
s1na Jul 6, 2021
b5b1743
eth/tracers: use addToObj in GetResult
s1na Jul 6, 2021
24932b9
core/vm: capture frames in struct and json logger
s1na Jul 8, 2021
92c0af9
eth/tracers: use String method of callFrameType
s1na Jul 8, 2021
22d4520
core/vm: fix frame marshaling in json logger
s1na Jul 8, 2021
611d084
core/vm: generate StructFrame marshaling via gencodec
s1na Jul 21, 2021
481839d
eth/tracers: Add js call frame tracer
s1na Jul 28, 2021
d25f8c8
core,eth: add err field to call frame tracing
s1na Jul 28, 2021
3aad776
core,eth: rm unnecessary env field frome enter and exit
s1na Jul 28, 2021
8bfc448
core/vm: rm extra fields from structFrameMarshaling
s1na Aug 2, 2021
fdb6341
core,eth: rename callframe type var to typ
s1na Aug 2, 2021
416a747
core/vm: move debug-code away from hot-code
holiman Sep 10, 2021
76e0849
core/vm: remove redefinition of calltypes
holiman Sep 10, 2021
ada0e6a
eth/tracers: fix crash in tracers
holiman Sep 10, 2021
ea632d4
core/vm: fix stack print output
holiman Sep 10, 2021
516d093
squashme: fix logger interface
holiman Sep 10, 2021
0f93be4
core/vm/runtime: more comprehensive tracer test
holiman Sep 10, 2021
c5dfb70
core/vm, eth/tracers: make it possible to omit step function
holiman Sep 10, 2021
f27caae
core/vm: fix crash
holiman Sep 12, 2021
0ee54b7
core/vm: add bench for tracer step vs callframe overhead
s1na Sep 12, 2021
c1fa4fe
core/vm: add create test case for tracer
s1na Sep 13, 2021
b8906d5
eth/tracers: minor fixes + testcase
holiman Sep 13, 2021
66dd419
core/vm: add create2 test case for tracer
s1na Sep 13, 2021
fe57b47
eth/tracers/testdata: add testcase containing selfdestruct
holiman Sep 13, 2021
bcb2f50
Merge branch 'trace-call-frame' of github.com:s1na/go-ethereum into t…
holiman Sep 13, 2021
82a41a0
core/vm: refactor runtime js tracer test
s1na Sep 13, 2021
6814a9d
core/vm: test tracer exit when calling selfdestructing contract
s1na Sep 13, 2021
5525eda
core/vm: add self-destruct as callframe to tracer
s1na Sep 13, 2021
386d04e
core/vm: rm extra comment
s1na Sep 13, 2021
b055bce
core/vm: use CopyBytes
s1na Sep 13, 2021
992ec6d
core/vm: minor
s1na Sep 13, 2021
27de81a
core/vm: dont emit enter&exit in JSONLogger
s1na Sep 13, 2021
78475bc
core/vm: dont collect frames in structlogger
s1na Sep 14, 2021
366ecfd
core/vm: minor
s1na Sep 14, 2021
4ad29ff
eth,core/vm: lazy-push callframe fields to JS
s1na Sep 14, 2021
52cc78c
eth/tracers: Omit output when error != nil in callframe tracer
s1na Sep 14, 2021
07240e5
core/vm: add tracer test for create txes
s1na Sep 14, 2021
f7f1520
eth/tracers: benchmark for calltracer vs scoped tracer
holiman Sep 14, 2021
57901b2
eth/tracers: copy call tracers test set for new vs legacy
s1na Sep 15, 2021
a691191
eth/tracers: minor test fix
s1na Sep 15, 2021
6361ce8
eth/tracers: fix innerCreateOogOuterThrow test for callFrameTracer
s1na Sep 15, 2021
7e6f83b
eth/tracers: fix callFrameTracer json test cases
s1na Sep 15, 2021
f156b3c
eth/tracers: enable callframeTracer tests
s1na Sep 15, 2021
8627eb5
eth/tracers: callTracer->callTracerLegacy, callframeTracer->callTracer
s1na Sep 16, 2021
6c76c46
eth/tracers: drop step from call_tracer
s1na Sep 16, 2021
3f79aaa
eth/tracers: simplify tracer benchmark
holiman Sep 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/vm/access_list_tracer.go
Expand Up @@ -166,6 +166,11 @@ func (*AccessListTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost

func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}

func (*AccessListTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}

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

// AccessList returns the current accesslist maintained by the tracer.
func (a *AccessListTracer) AccessList() types.AccessList {
return a.list.accessList()
Expand Down
65 changes: 53 additions & 12 deletions core/vm/evm.go
Expand Up @@ -193,11 +193,19 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)

// Capture the tracer start/end events in debug mode
if evm.Config.Debug && evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now())
if evm.Config.Debug {
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now())
} else {
// Handle tracer events for entering and exiting a call frame
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}
}

if isPrecompile {
Expand Down Expand Up @@ -257,6 +265,14 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
}
var snapshot = evm.StateDB.Snapshot()

// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Debug {
evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}

// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
Expand Down Expand Up @@ -293,6 +309,14 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
}
var snapshot = evm.StateDB.Snapshot()

// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Debug {
evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}

// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
Expand Down Expand Up @@ -338,6 +362,14 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
// future scenarios
evm.StateDB.AddBalance(addr, big0)

// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Debug {
evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}

if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
Expand Down Expand Up @@ -377,7 +409,7 @@ func (c *codeAndHash) Hash() common.Hash {
}

// create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
// Depth check execution. Fail if we're trying to execute above the
// limit.
if evm.depth > int(params.CallCreateDepth) {
Expand Down Expand Up @@ -415,9 +447,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
return nil, address, gas, nil
}

if evm.Config.Debug && evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
if evm.Config.Debug {
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
} else {
evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value)
}
}

start := time.Now()

ret, err := evm.interpreter.Run(contract, nil, false)
Expand Down Expand Up @@ -455,16 +492,20 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
}
}

if evm.Config.Debug && evm.depth == 0 {
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
if evm.Config.Debug {
if evm.depth == 0 {
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
} else {
evm.Config.Tracer.CaptureExit(ret, gas-contract.Gas, err)
}
}
return ret, address, contract.Gas, err
}

// Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr)
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE)
}

// Create2 creates a new contract using code as deployment code.
Expand All @@ -474,7 +515,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
codeAndHash := &codeAndHash{code: code}
contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
return evm.create(caller, codeAndHash, gas, endowment, contractAddr)
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2)
}

// ChainConfig returns the environment's chain configuration
Expand Down
4 changes: 4 additions & 0 deletions core/vm/instructions.go
Expand Up @@ -791,6 +791,10 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
interpreter.evm.StateDB.Suicide(scope.Contract.Address())
if interpreter.cfg.Debug {
interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil)
}
return nil, nil
}

Expand Down
12 changes: 12 additions & 0 deletions core/vm/logger.go
Expand Up @@ -106,6 +106,8 @@ func (s *StructLog) ErrorString() string {
type Tracer interface {
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
CaptureExit(output []byte, gasUsed uint64, err error)
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
}
Expand Down Expand Up @@ -225,6 +227,11 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration
}
}

func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}

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

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

Expand Down Expand Up @@ -342,3 +349,8 @@ func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, e
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err)
}

func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}

func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
5 changes: 5 additions & 0 deletions core/vm/logger_json.go
Expand Up @@ -87,3 +87,8 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration,
}
l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, errMsg})
}

func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}

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