From f915e82915b2d9958ba056665c913040cc10976c Mon Sep 17 00:00:00 2001 From: Philip Fan Date: Thu, 7 Jul 2022 18:09:42 +0800 Subject: [PATCH 1/3] eth/tracers: add initial returnMsg tracer --- eth/tracers/native/returnmsg.go | 108 ++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 eth/tracers/native/returnmsg.go diff --git a/eth/tracers/native/returnmsg.go b/eth/tracers/native/returnmsg.go new file mode 100644 index 000000000000..a655a0cd1cc4 --- /dev/null +++ b/eth/tracers/native/returnmsg.go @@ -0,0 +1,108 @@ +// Copyright 2022 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 . + +package native + +import ( + "bytes" + "encoding/json" + "math/big" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +func init() { + register("returnMsgTracer", newReturnMsgTracer) +} + +var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] + +// returnMsgTracer is a go implementation of the Tracer interface which +// track the error message return by the contract. +type returnMsgTracer struct { + env *vm.EVM + returnMsg string // The error message return from the tx, if tx success, empty string return + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption +} + +// newNoopTracer returns a new noop tracer. +func newReturnMsgTracer(_ *tracers.Context) tracers.Tracer { + return &returnMsgTracer{} +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *returnMsgTracer) CaptureStart(env *vm.EVM, _ common.Address, _ common.Address, _ bool, _ []byte, _ uint64, _ *big.Int) { + t.env = env +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *returnMsgTracer) CaptureEnd(output []byte, _ uint64, _ time.Duration, err error) { + if err != nil { + if err == vm.ErrExecutionReverted && len(output) > 4 && bytes.Equal(output[:4], revertSelector) { + returnMsg, _ := abi.UnpackRevert(output) + t.returnMsg = err.Error() + ": " + returnMsg + } else { + t.returnMsg = err.Error() + } + } +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *returnMsgTracer) CaptureState(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ []byte, _ int, _ error) { +} + +// CaptureFault implements the EVMLogger interface to trace an execution fault. +func (t *returnMsgTracer) CaptureFault(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ int, _ error) { +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *returnMsgTracer) CaptureEnter(_ vm.OpCode, _ common.Address, _ common.Address, _ []byte, _ uint64, _ *big.Int) { + // Skip if tracing was interrupted + if atomic.LoadUint32(&t.interrupt) > 0 { + t.env.Cancel() + return + } +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *returnMsgTracer) CaptureExit(_ []byte, _ uint64, _ error) {} + +func (t *returnMsgTracer) CaptureTxStart(_ uint64) {} + +func (t *returnMsgTracer) CaptureTxEnd(_ uint64) {} + +// GetResult returns an error message json object. +func (t *returnMsgTracer) GetResult() (json.RawMessage, error) { + res, err := json.Marshal(t.returnMsg) + if err != nil { + return nil, err + } + return res, t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *returnMsgTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} From 43261f841d9f68d0ac1acdd1b79e6746abaeabfe Mon Sep 17 00:00:00 2001 From: Philip Fan Date: Sat, 9 Jul 2022 11:38:12 +0800 Subject: [PATCH 2/3] Update eth/tracers/native/returnmsg.go Co-authored-by: Martin Holst Swende --- eth/tracers/native/returnmsg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/tracers/native/returnmsg.go b/eth/tracers/native/returnmsg.go index a655a0cd1cc4..1704b2028a7e 100644 --- a/eth/tracers/native/returnmsg.go +++ b/eth/tracers/native/returnmsg.go @@ -45,7 +45,7 @@ type returnMsgTracer struct { reason error // Textual reason for the interruption } -// newNoopTracer returns a new noop tracer. +// newReturnMsgTracer returns a new noop tracer. func newReturnMsgTracer(_ *tracers.Context) tracers.Tracer { return &returnMsgTracer{} } From a79304f3550d6b19b0e4ce028dd62a4412087215 Mon Sep 17 00:00:00 2001 From: Philip Fan Date: Tue, 12 Jul 2022 10:55:02 +0800 Subject: [PATCH 3/3] eth/tracers: rename to revertReasonTracer --- .../native/{returnmsg.go => revertreason.go} | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) rename eth/tracers/native/{returnmsg.go => revertreason.go} (56%) diff --git a/eth/tracers/native/returnmsg.go b/eth/tracers/native/revertreason.go similarity index 56% rename from eth/tracers/native/returnmsg.go rename to eth/tracers/native/revertreason.go index 1704b2028a7e..b402396cb065 100644 --- a/eth/tracers/native/returnmsg.go +++ b/eth/tracers/native/revertreason.go @@ -31,52 +31,52 @@ import ( ) func init() { - register("returnMsgTracer", newReturnMsgTracer) + register("revertReasonTracer", newRevertReasonTracer) } var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] -// returnMsgTracer is a go implementation of the Tracer interface which -// track the error message return by the contract. -type returnMsgTracer struct { - env *vm.EVM - returnMsg string // The error message return from the tx, if tx success, empty string return - interrupt uint32 // Atomic flag to signal execution interruption - reason error // Textual reason for the interruption +// revertReasonTracer is a go implementation of the Tracer interface which +// track the error message or revert reason return by the contract. +type revertReasonTracer struct { + env *vm.EVM + revertReason string // The revert reason return from the tx, if tx success, empty string return + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption } -// newReturnMsgTracer returns a new noop tracer. -func newReturnMsgTracer(_ *tracers.Context) tracers.Tracer { - return &returnMsgTracer{} +// newRevertReasonTracer returns a new revert reason tracer. +func newRevertReasonTracer(_ *tracers.Context) tracers.Tracer { + return &revertReasonTracer{} } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *returnMsgTracer) CaptureStart(env *vm.EVM, _ common.Address, _ common.Address, _ bool, _ []byte, _ uint64, _ *big.Int) { +func (t *revertReasonTracer) CaptureStart(env *vm.EVM, _ common.Address, _ common.Address, _ bool, _ []byte, _ uint64, _ *big.Int) { t.env = env } // CaptureEnd is called after the call finishes to finalize the tracing. -func (t *returnMsgTracer) CaptureEnd(output []byte, _ uint64, _ time.Duration, err error) { +func (t *revertReasonTracer) CaptureEnd(output []byte, _ uint64, _ time.Duration, err error) { if err != nil { if err == vm.ErrExecutionReverted && len(output) > 4 && bytes.Equal(output[:4], revertSelector) { - returnMsg, _ := abi.UnpackRevert(output) - t.returnMsg = err.Error() + ": " + returnMsg + errMsg, _ := abi.UnpackRevert(output) + t.revertReason = err.Error() + ": " + errMsg } else { - t.returnMsg = err.Error() + t.revertReason = err.Error() } } } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *returnMsgTracer) CaptureState(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ []byte, _ int, _ error) { +func (t *revertReasonTracer) CaptureState(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ []byte, _ int, _ error) { } // CaptureFault implements the EVMLogger interface to trace an execution fault. -func (t *returnMsgTracer) CaptureFault(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ int, _ error) { +func (t *revertReasonTracer) CaptureFault(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ int, _ error) { } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *returnMsgTracer) CaptureEnter(_ vm.OpCode, _ common.Address, _ common.Address, _ []byte, _ uint64, _ *big.Int) { +func (t *revertReasonTracer) CaptureEnter(_ vm.OpCode, _ common.Address, _ common.Address, _ []byte, _ uint64, _ *big.Int) { // Skip if tracing was interrupted if atomic.LoadUint32(&t.interrupt) > 0 { t.env.Cancel() @@ -86,15 +86,15 @@ func (t *returnMsgTracer) CaptureEnter(_ vm.OpCode, _ common.Address, _ common.A // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *returnMsgTracer) CaptureExit(_ []byte, _ uint64, _ error) {} +func (t *revertReasonTracer) CaptureExit(_ []byte, _ uint64, _ error) {} -func (t *returnMsgTracer) CaptureTxStart(_ uint64) {} +func (t *revertReasonTracer) CaptureTxStart(_ uint64) {} -func (t *returnMsgTracer) CaptureTxEnd(_ uint64) {} +func (t *revertReasonTracer) CaptureTxEnd(_ uint64) {} // GetResult returns an error message json object. -func (t *returnMsgTracer) GetResult() (json.RawMessage, error) { - res, err := json.Marshal(t.returnMsg) +func (t *revertReasonTracer) GetResult() (json.RawMessage, error) { + res, err := json.Marshal(t.revertReason) if err != nil { return nil, err } @@ -102,7 +102,7 @@ func (t *returnMsgTracer) GetResult() (json.RawMessage, error) { } // Stop terminates execution of the tracer at the first opportune moment. -func (t *returnMsgTracer) Stop(err error) { +func (t *revertReasonTracer) Stop(err error) { t.reason = err atomic.StoreUint32(&t.interrupt, 1) }