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

[api] implement web3 API debug_traceTransaction #3779

Merged
merged 16 commits into from
Feb 17, 2023
31 changes: 31 additions & 0 deletions api/coreservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"strconv"
"time"

"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/pkg/errors"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
Expand All @@ -26,6 +28,7 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/iotexproject/go-pkgs/hash"
"github.com/iotexproject/go-pkgs/util"
"github.com/iotexproject/iotex-address/address"
"github.com/iotexproject/iotex-election/committee"
"github.com/iotexproject/iotex-proto/golang/iotexapi"
Expand Down Expand Up @@ -140,6 +143,8 @@ type (
ReceiveBlock(blk *block.Block) error
// BlockHashByBlockHeight returns block hash by block height
BlockHashByBlockHeight(blkHeight uint64) (hash.Hash256, error)
// TraceTransaction returns the trace result of a transaction
TraceTransaction(ctx context.Context, actHash string, config *logger.Config) ([]byte, *action.Receipt, *logger.StructLogger, error)
}

// coreService implements the CoreService interface
Expand Down Expand Up @@ -1582,3 +1587,29 @@ func (core *coreService) SyncingProgress() (uint64, uint64, uint64) {
startingHeight, currentHeight, targetHeight, _ := core.bs.SyncStatus()
return startingHeight, currentHeight, targetHeight
}

// TraceTransaction returns the trace result of transaction
func (core *coreService) TraceTransaction(ctx context.Context, actHash string, config *logger.Config) (retval []byte, receipt *action.Receipt, traces *logger.StructLogger, err error) {
Copy link
Member

Choose a reason for hiding this comment

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

grpcserver.TraceTransactionStructLogs should be integrated with this func

actInfo, err := core.Action(util.Remove0xPrefix(actHash), false)
if err != nil {
return
millken marked this conversation as resolved.
Show resolved Hide resolved
}
act, err := (&action.Deserializer{}).SetEvmNetworkID(core.EVMNetworkID()).ActionToSealedEnvelope(actInfo.Action)
if err != nil {
return
}
sc, ok := act.Action().(*action.Execution)
if !ok {
err = status.Error(codes.InvalidArgument, "the type of action is not supported")
millken marked this conversation as resolved.
Show resolved Hide resolved
return
}
traces = logger.NewStructLogger(nil)
ctx = protocol.WithVMConfigCtx(ctx, vm.Config{
Debug: true,
Tracer: traces,
NoBaseFee: true,
})
addr, _ := address.FromString(address.ZeroAddress)
retval, receipt, err = core.SimulateExecution(ctx, addr, sc)
return
}
61 changes: 61 additions & 0 deletions api/web3server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (
"strconv"
"time"

"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/iotexproject/go-pkgs/hash"
"github.com/iotexproject/go-pkgs/util"
"github.com/iotexproject/iotex-address/address"
Expand Down Expand Up @@ -205,6 +207,8 @@ func (svr *web3Handler) handleWeb3Req(ctx context.Context, web3Req *gjson.Result
res, err = svr.subscribe(web3Req, writer)
case "eth_unsubscribe":
res, err = svr.unsubscribe(web3Req)
case "debug_traceTransaction":
res, err = svr.traceTransaction(ctx, web3Req)
case "eth_coinbase", "eth_getUncleCountByBlockHash", "eth_getUncleCountByBlockNumber",
"eth_sign", "eth_signTransaction", "eth_sendTransaction", "eth_getUncleByBlockHashAndIndex",
"eth_getUncleByBlockNumberAndIndex", "eth_pendingTransactions":
Expand Down Expand Up @@ -880,6 +884,63 @@ func (svr *web3Handler) unsubscribe(in *gjson.Result) (interface{}, error) {
return chainListener.RemoveResponder(id.String())
}

func (svr *web3Handler) traceTransaction(ctx context.Context, in *gjson.Result) (interface{}, error) {
actHash, options := in.Get("params.0"), in.Get("params.1")
if !actHash.Exists() {
return nil, errInvalidFormat
}
hash := util.Remove0xPrefix(actHash.String())
if len(hash) != 64 {
millken marked this conversation as resolved.
Show resolved Hide resolved
return nil, errInvalidFormat
}
var (
enableMemory, disableStack, disableStorage, enableReturnData bool
)
if options.Exists() {
enableMemory = options.Get("enableMemory").Bool()
disableStack = options.Get("disableStack").Bool()
disableStorage = options.Get("disableStorage").Bool()
enableReturnData = options.Get("enableReturnData").Bool()
}
cfg := &logger.Config{
EnableMemory: enableMemory,
DisableStack: disableStack,
DisableStorage: disableStorage,
EnableReturnData: enableReturnData,
}
retval, receipt, traces, err := svr.coreService.TraceTransaction(ctx, hash, cfg)
if err != nil {
return nil, err
}

structLogs := make([]structLog, 0)
for _, s := range traces.StructLogs() {
var enc structLog
enc.Pc = s.Pc
enc.Op = s.Op
enc.Gas = math.HexOrDecimal64(s.Gas)
enc.GasCost = math.HexOrDecimal64(s.GasCost)
enc.Memory = s.Memory
enc.MemorySize = s.MemorySize
enc.Stack = s.Stack
enc.ReturnData = s.ReturnData
enc.Storage = s.Storage
enc.Depth = s.Depth
enc.RefundCounter = s.RefundCounter
enc.OpName = s.OpName()
enc.ErrorString = s.ErrorString()
structLogs = append(structLogs, enc)
}

return &debugTraceTransactionResult{
Failed: receipt.Status != uint64(iotextypes.ReceiptStatus_Success),
Revert: receipt.ExecutionRevertMsg(),
ReturnValue: byteToHex(retval),
StructLogs: structLogs,
Gas: receipt.GasConsumed,
}, nil
}

func (svr *web3Handler) unimplemented() (interface{}, error) {
return nil, errNotImplemented
}
27 changes: 27 additions & 0 deletions api/web3server_marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import (
"encoding/hex"
"encoding/json"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/holiman/uint256"
"github.com/iotexproject/go-pkgs/crypto"
"github.com/iotexproject/go-pkgs/hash"
"github.com/iotexproject/iotex-address/address"
Expand Down Expand Up @@ -75,6 +80,28 @@ type (
CurrentBlock string `json:"currentBlock"`
HighestBlock string `json:"highestBlock"`
}
structLog struct {
Copy link
Member

Choose a reason for hiding this comment

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

if custom marshalling is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, structLog = logger.StructLog, but logger.StructLog ignored json field storage, web3 API needs to export it

Copy link
Member

Choose a reason for hiding this comment

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

it could be done in MarshalJSON()

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 think there is no need to implement marshalJSON here, the structLog field can already be automatically marshaled inside. use interface{} is harder to understand

Pc uint64 `json:"pc"`
Op vm.OpCode `json:"op"`
Gas math.HexOrDecimal64 `json:"gas"`
GasCost math.HexOrDecimal64 `json:"gasCost"`
Memory hexutil.Bytes `json:"memory"`
MemorySize int `json:"memSize"`
Stack []uint256.Int `json:"stack"`
ReturnData hexutil.Bytes `json:"returnData"`
Storage map[common.Hash]common.Hash `json:"storage"`
Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"`
OpName string `json:"opName"`
ErrorString string `json:"error"`
}
debugTraceTransactionResult struct {
Failed bool `json:"failed"`
Revert string `json:"revert"`
ReturnValue string `json:"returnValue"`
Gas uint64 `json:"gas"`
StructLogs []structLog `json:"structLogs"`
}
)

var (
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ require (
)

require (
github.com/holiman/uint256 v1.2.0
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible
github.com/shirou/gopsutil/v3 v3.22.2
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0
Expand Down Expand Up @@ -94,7 +95,6 @@ require (
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 // indirect
github.com/holiman/uint256 v1.2.0 // indirect
github.com/huin/goupnp v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/ipfs/go-cid v0.0.7 // indirect
Expand Down
18 changes: 18 additions & 0 deletions test/mock/mock_apicoreservice/mock_apicoreservice.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.