Skip to content

Commit

Permalink
add new debug api: trace call
Browse files Browse the repository at this point in the history
  • Loading branch information
libotony committed Aug 9, 2023
1 parent fc8771e commit 02fe658
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 18 deletions.
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func New(
Mount(router, "/blocks")
transactions.New(repo, txPool).
Mount(router, "/transactions")
debug.New(repo, stater, forkConfig).
debug.New(repo, stater, forkConfig, callGasLimit).
Mount(router, "/debug")
node.New(nw).
Mount(router, "/node")
Expand Down
209 changes: 197 additions & 12 deletions api/debug/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package debug

import (
"context"
"math"
"math/big"
"net/http"
"strconv"
"strings"
Expand All @@ -26,22 +28,26 @@ import (
"github.com/vechain/thor/tracers"
"github.com/vechain/thor/tracers/logger"
"github.com/vechain/thor/trie"
"github.com/vechain/thor/tx"
"github.com/vechain/thor/vm"
"github.com/vechain/thor/xenv"
)

var devNetGenesisID = genesis.NewDevnet().ID()

type Debug struct {
repo *chain.Repository
stater *state.Stater
forkConfig thor.ForkConfig
repo *chain.Repository
stater *state.Stater
forkConfig thor.ForkConfig
callGasLimit uint64
}

func New(repo *chain.Repository, stater *state.Stater, forkConfig thor.ForkConfig) *Debug {
func New(repo *chain.Repository, stater *state.Stater, forkConfig thor.ForkConfig, callGaslimit uint64) *Debug {
return &Debug{
repo,
stater,
forkConfig,
callGaslimit,
}
}

Expand Down Expand Up @@ -124,13 +130,10 @@ func (d *Debug) traceClause(ctx context.Context, tracer tracers.Tracer, blockID
}

func (d *Debug) handleTraceClause(w http.ResponseWriter, req *http.Request) error {
var opt *TracerOption
var opt TraceClauseOption
if err := utils.ParseJSON(req.Body, &opt); err != nil {
return utils.BadRequest(errors.WithMessage(err, "body"))
}
if opt == nil {
return utils.BadRequest(errors.New("body: empty body"))
}
var tracer tracers.Tracer
if opt.Name == "" {
tr, err := logger.NewStructLogger(opt.Config)
Expand Down Expand Up @@ -160,6 +163,91 @@ func (d *Debug) handleTraceClause(w http.ResponseWriter, req *http.Request) erro
return utils.WriteJSON(w, res)
}

func (d *Debug) handleTraceCall(w http.ResponseWriter, req *http.Request) error {
var opt TraceCallOption
if err := utils.ParseJSON(req.Body, &opt); err != nil {
return utils.BadRequest(errors.WithMessage(err, "body"))
}

summary, err := d.handleRevision(req.URL.Query().Get("revision"))
if err != nil {
return err
}

var tracer tracers.Tracer
if opt.Name == "" {
tr, err := logger.NewStructLogger(opt.Config)
if err != nil {
return err
}
tracer = tr
} else {
name := opt.Name
if !strings.HasSuffix(name, "Tracer") {
name += "Tracer"
}
tr, err := tracers.DefaultDirectory.New(name, opt.Config)
if err != nil {
return err
}
tracer = tr
}

txCtx, gas, clause, err := d.handleTraceCallOption(&opt)
if err != nil {
return err
}

res, err := d.traceCall(req.Context(), tracer, summary, txCtx, gas, clause)
if err != nil {
return err
}

return utils.WriteJSON(w, res)
}

func (d *Debug) traceCall(ctx context.Context, tracer tracers.Tracer, summary *chain.BlockSummary, txCtx *xenv.TransactionContext, gas uint64, clause *tx.Clause) (interface{}, error) {
header := summary.Header
state := d.stater.NewState(header.StateRoot(), header.Number(), summary.Conflicts, summary.SteadyNum)
signer, _ := header.Signer()
rt := runtime.New(
d.repo.NewChain(header.ID()),
state,
&xenv.BlockContext{
Beneficiary: header.Beneficiary(),
Signer: signer,
Number: header.Number(),
Time: header.Timestamp(),
GasLimit: header.GasLimit(),
TotalScore: header.TotalScore(),
},
d.forkConfig)

tracer.SetContext(&tracers.Context{
BlockID: summary.Header.ID(),
BlockTime: summary.Header.Timestamp(),
State: state,
})
rt.SetVMConfig(vm.Config{Debug: true, Tracer: tracer})

errCh := make(chan error, 1)
exec, interrupt := rt.PrepareClause(clause, 0, gas, txCtx)
go func() {
_, _, err := exec()
errCh <- err
}()
select {
case <-ctx.Done():
interrupt()
return nil, ctx.Err()
case err := <-errCh:
if err != nil {
return nil, err
}
}
return tracer.GetResult()
}

func (d *Debug) debugStorage(ctx context.Context, contractAddress thor.Address, blockID thor.Bytes32, txIndex uint64, clauseIndex uint64, keyStart []byte, maxResult int) (*StorageRangeResult, error) {
rt, _, _, err := d.prepareClauseEnv(ctx, blockID, txIndex, clauseIndex)
if err != nil {
Expand Down Expand Up @@ -194,13 +282,10 @@ func storageRangeAt(t *muxdb.Trie, start []byte, maxResult int) (*StorageRangeRe
}

func (d *Debug) handleDebugStorage(w http.ResponseWriter, req *http.Request) error {
var opt *StorageRangeOption
var opt StorageRangeOption
if err := utils.ParseJSON(req.Body, &opt); err != nil {
return utils.BadRequest(errors.WithMessage(err, "body"))
}
if opt == nil {
return utils.BadRequest(errors.New("body: empty body"))
}
blockID, txIndex, clauseIndex, err := d.parseTarget(opt.Target)
if err != nil {
return err
Expand Down Expand Up @@ -257,10 +342,110 @@ func (d *Debug) parseTarget(target string) (blockID thor.Bytes32, txIndex uint64
return
}

func (d *Debug) handleRevision(revision string) (*chain.BlockSummary, error) {
if revision == "" || revision == "best" {
return d.repo.BestBlockSummary(), nil
}
if len(revision) == 66 || len(revision) == 64 {
blockID, err := thor.ParseBytes32(revision)
if err != nil {
return nil, utils.BadRequest(errors.WithMessage(err, "revision"))
}
summary, err := d.repo.GetBlockSummary(blockID)
if err != nil {
if d.repo.IsNotFound(err) {
return nil, utils.BadRequest(errors.WithMessage(err, "revision"))
}
return nil, err
}
return summary, nil
}
n, err := strconv.ParseUint(revision, 0, 0)
if err != nil {
return nil, utils.BadRequest(errors.WithMessage(err, "revision"))
}
if n > math.MaxUint32 {
return nil, utils.BadRequest(errors.WithMessage(errors.New("block number out of max uint32"), "revision"))
}
summary, err := d.repo.NewBestChain().GetBlockSummary(uint32(n))
if err != nil {
if d.repo.IsNotFound(err) {
return nil, utils.BadRequest(errors.WithMessage(err, "revision"))
}
return nil, err
}
return summary, nil
}

func (d *Debug) handleTraceCallOption(opt *TraceCallOption) (*xenv.TransactionContext, uint64, *tx.Clause, error) {
gas := opt.Gas
if opt.Gas > d.callGasLimit {
return nil, 0, nil, utils.Forbidden(errors.New("gas: exceeds limit"))
} else if opt.Gas == 0 {
gas = d.callGasLimit
}

var txCtx xenv.TransactionContext
if opt.GasPrice == nil {
txCtx.GasPrice = new(big.Int)
} else {
txCtx.GasPrice = (*big.Int)(opt.GasPrice)
}
if opt.Caller == nil {
txCtx.Origin = thor.Address{}
} else {
txCtx.Origin = *opt.Caller
}
if opt.GasPayer == nil {
txCtx.GasPayer = thor.Address{}
} else {
txCtx.GasPayer = *opt.GasPayer
}
if opt.ProvedWork == nil {
txCtx.ProvedWork = new(big.Int)
} else {
txCtx.ProvedWork = (*big.Int)(opt.ProvedWork)
}
txCtx.Expiration = opt.Expiration

if len(opt.BlockRef) > 0 {
blockRef, err := hexutil.Decode(opt.BlockRef)
if err != nil {
return nil, 0, nil, errors.WithMessage(err, "blockRef")
}
if len(blockRef) != 8 {
return nil, 0, nil, errors.New("blockRef: invalid length")
}
var blkRef tx.BlockRef
copy(blkRef[:], blockRef[:])
txCtx.BlockRef = blkRef
}

var value *big.Int
if opt.Value == nil {
value = new(big.Int)
} else {
value = (*big.Int)(opt.Value)
}

var data []byte
var err error
if opt.Data != "" {
data, err = hexutil.Decode(opt.Data)
if err != nil {
return nil, 0, nil, utils.BadRequest(errors.WithMessage(err, "data"))
}
}

clause := tx.NewClause(opt.To).WithValue(value).WithData(data)
return &txCtx, gas, clause, nil
}

func (d *Debug) Mount(root *mux.Router, pathPrefix string) {
sub := root.PathPrefix(pathPrefix).Subrouter()

sub.Path("/tracers").Methods(http.MethodPost).HandlerFunc(utils.WrapHandlerFunc(d.handleTraceClause))
sub.Path("/tracers/call").Methods(http.MethodPost).HandlerFunc(utils.WrapHandlerFunc(d.handleTraceCall))
sub.Path("/storage-range").Methods(http.MethodPost).HandlerFunc(utils.WrapHandlerFunc(d.handleDebugStorage))

}
25 changes: 20 additions & 5 deletions api/debug/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,29 @@ package debug
import (
"encoding/json"

"github.com/ethereum/go-ethereum/common/math"
"github.com/vechain/thor/thor"
)

type TracerOption struct {
Name string `json:"name"`
Target string `json:"target"`
// Config specific to given tracer.
Config json.RawMessage `json:"config"`
type TraceClauseOption struct {
Name string `json:"name"`
Target string `json:"target"`
Config json.RawMessage `json:"config"` // Config specific to given tracer.
}

type TraceCallOption struct {
To *thor.Address `json:"to"`
Value *math.HexOrDecimal256 `json:"value"`
Data string `json:"data"`
Gas uint64 `json:"gas"`
GasPrice *math.HexOrDecimal256 `json:"gasPrice"`
ProvedWork *math.HexOrDecimal256 `json:"provedWork"`
Caller *thor.Address `json:"caller"`
GasPayer *thor.Address `json:"gasPayer"`
Expiration uint32 `json:"expiration"`
BlockRef string `json:"blockRef"`
Name string `json:"name"` // Tracer
Config json.RawMessage `json:"config"` // Config specific to given tracer.
}

type StorageRangeOption struct {
Expand Down

0 comments on commit 02fe658

Please sign in to comment.