Skip to content

Commit

Permalink
Debug transaction using executor-like traces (0xPolygonHermez#738)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* WIP

* WIP

* WIP

* jsTracer WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* FakeDB implementation

* Refactor

* WIP

* WIP

* Delete DS_Store files

* Add DS_Store to gitignore

* Linter

* go mod tidy

* WIP

* WIP

* Add Storage

* Finish

* fix

* Fix executor server port in config.toml

* Fix base16 value

* integrate debug_traceTransaction with state method

* Trace fixes

* linter

* WIP: fixed JSON RPC interface for debug_traceTransaction

* Fixes

* Fix deploy tracing

* Manage memory as bytes

* Fix parsing problem

* Add reverted reason when possible

Co-authored-by: tclemos <[email protected]>
  • Loading branch information
ToniRamirezM and tclemos authored Jun 15, 2022
1 parent 2199903 commit 6550cf9
Show file tree
Hide file tree
Showing 76 changed files with 137,538 additions and 837 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
/test/contracts/bin/**/*.bin
/test/contracts/bin/**/*.abi

**/.DS_Store
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ generate-code-from-proto: ## Generates code from proto files
cd proto/src/proto/mt/v1 && protoc --proto_path=. --go_out=../../../../../state/tree/pb --go-grpc_out=../../../../../state/tree/pb --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative mt.proto
cd proto/src/proto/zkprover/v1 && protoc --proto_path=. --go_out=../../../../../proverclient/pb --go-grpc_out=../../../../../proverclient/pb --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative zk-prover.proto
cd proto/src/proto/zkprover/v1 && protoc --proto_path=. --go_out=../../../../../proverservice/pb --go-grpc_out=../../../../../proverservice/pb --go-grpc_opt=paths=source_relative --go_opt=paths=source_relative zk-prover.proto
cd proto/src/proto/executor/v1 && protoc --proto_path=. --go_out=../../../../../state/runtime/executor/pb --go-grpc_out=../../../../../state/runtime/executor/pb --go-grpc_opt=paths=source_relative --go_opt=paths=source_relative executor.proto

.PHONY: update-external-dependencies
update-external-dependencies: ## Updates external dependencies like images, test vectors or proto files
Expand Down
6 changes: 5 additions & 1 deletion config/config.debug.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ PrivateKeyPassword = "testonly"
[RPC]
Host = "0.0.0.0"
Port = 8123
MaxRequestsPerIPAndSecond = 10
MaxRequestsPerIPAndSecond = 100

[Synchronizer]
SyncInterval = "5s"
Expand Down Expand Up @@ -66,3 +66,7 @@ StoreBackend = "PostgreSQL"

[MTClient]
URI = "127.0.0.1:50060"

[ExecutorServer]
Host = "0.0.0.0"
Port = 0
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/hermeznetwork/hermez-core/log"
"github.com/hermeznetwork/hermez-core/proverclient"
"github.com/hermeznetwork/hermez-core/sequencer"
"github.com/hermeznetwork/hermez-core/state/runtime/executor"
"github.com/hermeznetwork/hermez-core/state/tree"
"github.com/hermeznetwork/hermez-core/synchronizer"
"github.com/mitchellh/mapstructure"
Expand Down Expand Up @@ -46,6 +47,7 @@ type Config struct {
GasPriceEstimator gasprice.Config
MTServer tree.ServerConfig
MTClient tree.ClientConfig
ExecutorServer executor.ServerConfig
}

// Load loads the configuration
Expand Down
4 changes: 4 additions & 0 deletions config/config.local.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ StoreBackend = "PostgreSQL"

[MTClient]
URI = "127.0.0.1:50060"

[ExecutorServer]
Host = "0.0.0.0"
Port = 0
8 changes: 8 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ func Test_Defaults(t *testing.T) {
path: "RPC.MaxRequestsPerIPAndSecond",
expectedValue: float64(50),
},
{
path: "ExecutorServer.Host",
expectedValue: "0.0.0.0",
},
{
path: "ExecutorServer.Port",
expectedValue: 0,
},
{
path: "RPC.ChainID",
expectedValue: uint64(1001),
Expand Down
4 changes: 4 additions & 0 deletions config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,8 @@ StoreBackend = "PostgreSQL"
[MTClient]
URI = "127.0.0.1:50060"
[ExecutorServer]
Host = "0.0.0.0"
Port = 0
`
2 changes: 2 additions & 0 deletions encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
const (
// Base10 decimal base
Base10 = 10
// Base16 hexadecimal base
Base16 = 16
// BitSize64 64 bits
BitSize64 = 64
// TenToThePowerOf18 represents 1000000000000000000
Expand Down
12 changes: 9 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/dgraph-io/badger/v3 v3.2103.2
github.com/dgraph-io/ristretto v0.1.0
github.com/didip/tollbooth/v6 v6.1.2
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf
github.com/ethereum/go-ethereum v1.10.18
github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.4.2
Expand Down Expand Up @@ -34,6 +35,13 @@ require (
gotest.tools v2.2.0+incompatible
)

require (
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/valyala/fastjson v1.4.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
)

require (
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
Expand Down Expand Up @@ -69,7 +77,7 @@ require (
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/holiman/uint256 v1.2.0 // indirect
github.com/holiman/uint256 v1.2.0
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
Expand Down Expand Up @@ -106,9 +114,7 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
github.com/valyala/fastjson v1.4.1 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/didip/tollbooth/v6 v6.1.2 h1:Kdqxmqw9YTv0uKajBUiWQg+GURL/k4vy9gmLCL01PjQ=
github.com/didip/tollbooth/v6 v6.1.2/go.mod h1:xjcse6CTHCLuOkzsWrEgdy9WPJFv+p/x6v+MyfP+O9s=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
Expand All @@ -237,6 +238,7 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ=
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
Expand Down Expand Up @@ -310,6 +312,7 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-pkgz/expirable-cache v0.0.3 h1:rTh6qNPp78z0bQE6HDhXBHUwqnV9i09Vm6dksJLXQDc=
github.com/go-pkgz/expirable-cache v0.0.3/go.mod h1:+IauqN00R2FqNRLCLA+X5YljQJrwB179PfiAoMPlTlQ=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
Expand Down
104 changes: 74 additions & 30 deletions jsonrpc/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ package jsonrpc

import (
"context"
"errors"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/hermeznetwork/hermez-core/state"
"github.com/hermeznetwork/hermez-core/log"
)

// Debug is the debug jsonrpc endpoint
type Debug struct {
state stateInterface
}

type traceConfig struct {
Tracer *string `json:"tracer"`
}

type traceTransactionResponse struct {
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"`
ReturnValue argBytes `json:"returnValue"`
StructLogs []StructLogRes `json:"structLogs"`
}

Expand All @@ -28,47 +30,89 @@ type StructLogRes struct {
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error string `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Stack *[]argBig `json:"stack,omitempty"`
Memory *argBytes `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
RefundCounter uint64 `json:"refund,omitempty"`
}

// TraceTransaction creates a response for debug_traceTransaction request.
// See https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracetransaction
func (d *Debug) TraceTransaction(hash common.Hash) (interface{}, error) {
func (d *Debug) TraceTransaction(hash common.Hash, cfg *traceConfig) (interface{}, error) {
ctx := context.Background()

// tx, err := d.state.GetTransactionByHash(ctx, hash, "")
// if errors.Is(err, state.ErrNotFound) {
// return newGenericError("transaction not found", -32000), nil
// }
tracer := ""
if cfg != nil && cfg.Tracer != nil {
tracer = *cfg.Tracer
}

result, err := d.state.DebugTransaction(ctx, hash, tracer)
if err != nil {
const errorMessage = "failed to debug trace the transaction"
log.Debugf("%v: %v", errorMessage, err)
return nil, newRPCError(defaultErrorCode, errorMessage)
}

rcpt, err := d.state.GetTransactionReceipt(ctx, hash, "")
if errors.Is(err, state.ErrNotFound) {
return newRPCError(defaultErrorCode, "transaction receipt not found"), nil
if tracer != "" && len(result.ExecutorTraceResult) > 0 {
return result.ExecutorTraceResult, nil
}

failed := false
returnValue := ""
if rcpt.Status == types.ReceiptStatusFailed {
failed = true
// this is supposed to be an object value that will be parsed by blockscout
// to identify the details of the transaction execution.
// When this field is different from nil, it make the blockscout UI to show
// the transaction as Failed, but as long as we don't know exactly the object
// we need to return here, this is causing parsing errors on blockscout, which
// is causing a side effect to send multiple requests to the core, retrying to
// get this information.
// TODO: we need to figure out what to return here based on geth code.
returnValue = "{}"
failed := result.Failed()
structLogs := make([]StructLogRes, 0, len(result.StructLogs))
for _, structLog := range result.StructLogs {
var stackRes *[]argBig
if len(structLog.Stack) > 0 {
stack := make([]argBig, 0, len(structLog.Stack))
for _, stackItem := range structLog.Stack {
if stackItem != nil {
stack = append(stack, argBig(*stackItem))
}
}
stackRes = &stack
}

var memoryRes *argBytes
if len(structLog.Memory) > 0 {
memory := make(argBytes, 0, len(structLog.Memory))
for _, memoryItem := range structLog.Memory {
memory = append(memory, memoryItem)
}
memoryRes = &memory
}

var storageRes *map[string]string
if len(structLog.Storage) > 0 {
storage := make(map[string]string, len(structLog.Storage))
for storageKey, storageValue := range structLog.Storage {
storage[storageKey.Hex()] = storageValue.Hex()
}
storageRes = &storage
}

errRes := ""
if structLog.Err != nil {
errRes = structLog.Err.Error()
}

structLogs = append(structLogs, StructLogRes{
Pc: structLog.Pc,
Op: structLog.Op,
Gas: structLog.Gas,
GasCost: structLog.GasCost,
Depth: structLog.Depth,
Error: errRes,
Stack: stackRes,
Memory: memoryRes,
Storage: storageRes,
RefundCounter: structLog.RefundCounter,
})
}

resp := traceTransactionResponse{
Gas: rcpt.GasUsed,
Gas: result.GasUsed,
Failed: failed,
ReturnValue: returnValue,
StructLogs: []StructLogRes{},
ReturnValue: result.ReturnValue,
StructLogs: structLogs,
}

return resp, nil
Expand Down
5 changes: 1 addition & 4 deletions jsonrpc/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ type Handler struct {
}

func newJSONRpcHandler(ethEndpoints *Eth, netEndpoints *Net,
hezEndpoints *Hez, txPoolEndpoints *TxPool, traceEndpoints *Trace,
parityEndpoints *Parity, debugEndpoints *Debug, web3Endpoints *Web3) *Handler {
hezEndpoints *Hez, txPoolEndpoints *TxPool, debugEndpoints *Debug, web3Endpoints *Web3) *Handler {
handler := &Handler{
serviceMap: map[string]*serviceData{},
}
Expand All @@ -46,8 +45,6 @@ func newJSONRpcHandler(ethEndpoints *Eth, netEndpoints *Net,
handler.registerService("net", netEndpoints)
handler.registerService("hez", hezEndpoints)
handler.registerService("txpool", txPoolEndpoints)
handler.registerService("trace", traceEndpoints)
handler.registerService("parity", parityEndpoints)
handler.registerService("debug", debugEndpoints)
handler.registerService("web3", web3Endpoints)

Expand Down
3 changes: 1 addition & 2 deletions jsonrpc/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,11 @@ type stateInterface interface {
GetTransactionByBatchNumberAndIndex(ctx context.Context, batchNumber uint64, index uint64, txBundleID string) (*types.Transaction, error)
GetNonce(ctx context.Context, address common.Address, batchNumber uint64, txBundleID string) (uint64, error)
GetBatchHeader(ctx context.Context, batchNumber uint64, txBundleID string) (*types.Header, error)
ReplayTransaction(transactionHash common.Hash, traceMode []string) *runtime.ExecutionResult
ReplayBatchTransactions(batchNumber uint64, traceMode []string) ([]*runtime.ExecutionResult, error)
GetBatchTransactionCountByHash(ctx context.Context, hash common.Hash, txBundleID string) (uint64, error)
GetBatchTransactionCountByNumber(ctx context.Context, batchNumber uint64, txBundleID string) (uint64, error)
GetLogs(ctx context.Context, fromBatch uint64, toBatch uint64, addresses []common.Address, topics [][]common.Hash, batchHash *common.Hash, since *time.Time, txBundleID string) ([]*types.Log, error)
GetBatchHashesSince(ctx context.Context, since time.Time, txBundleID string) ([]common.Hash, error)
DebugTransaction(ctx context.Context, transactionHash common.Hash, tracer string) (*runtime.ExecutionResult, error)
}

type storageInterface interface {
Expand Down
Loading

0 comments on commit 6550cf9

Please sign in to comment.