WIP: create the 'debug_traceRip7560Validation' API

This commit is contained in:
Alex Forshtat 2024-06-04 20:30:51 +02:00
parent 8064306e4b
commit 94156da356
4 changed files with 220 additions and 6 deletions

View file

@ -1022,6 +1022,9 @@ func APIs(backend Backend) []rpc.API {
{
Namespace: "debug",
Service: NewAPI(backend),
}, {
Namespace: "debug",
Service: NewRip7560API(backend),
},
}
}

View file

@ -0,0 +1,141 @@
package tracers
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
"log"
"math/big"
"time"
)
// Rip7560API is the collection of tracing APIs exposed over the private debugging endpoint.
type Rip7560API struct {
backend Backend
}
func NewRip7560API(backend Backend) *Rip7560API {
return &Rip7560API{backend: backend}
}
// TraceRip7560Validation mostly copied from 'tracers/api.go' file
func (api *Rip7560API) TraceRip7560Validation(
ctx context.Context,
args ethapi.TransactionArgs,
blockNrOrHash rpc.BlockNumberOrHash,
config *TraceCallConfig,
) (interface{}, error) {
number, _ := blockNrOrHash.Number()
block, err := api.blockByNumber(ctx, number)
if err != nil {
return nil, err
}
reexec := defaultTraceReexec
statedb, release, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false)
if err != nil {
return nil, err
}
defer release()
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil {
return nil, err
}
var (
msg = args.ToMessage(vmctx.BaseFee)
tx = args.ToTransaction()
traceConfig *TraceConfig
)
if config != nil {
traceConfig = &config.TraceConfig
}
traceResult, err := api.traceTx(ctx, tx, msg, new(Context), vmctx, statedb, traceConfig)
if err != nil {
return nil, err
}
log.Println("TraceRip7560Validation result")
log.Println(string(traceResult.(json.RawMessage)))
return traceResult, err
}
//////// copy-pasted code
// blockByNumber is the wrapper of the chain access function offered by the backend.
// It will return an error if the block is not found.
func (api *Rip7560API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
block, err := api.backend.BlockByNumber(ctx, number)
if err != nil {
return nil, err
}
if block == nil {
return nil, fmt.Errorf("block #%d not found", number)
}
return block, nil
}
// chainContext constructs the context reader which is used by the evm for reading
// the necessary chain context.
func (api *Rip7560API) chainContext(ctx context.Context) core.ChainContext {
return ethapi.NewChainContext(ctx, api.backend)
}
func (api *Rip7560API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
var (
tracer *Tracer
err error
timeout = defaultTraceTimeout
usedGas uint64
)
if config == nil {
config = &TraceConfig{}
}
// Default tracer is the struct logger
//if config.Tracer == nil {
// logger := logger.NewStructLogger(config.Config)
// tracer = &Tracer{
// Hooks: logger.Hooks(),
// GetResult: logger.GetResult,
// Stop: logger.Stop,
// }
//} else {
tracer, err = DefaultDirectory.New("rip7560Validation", txctx, config.TracerConfig)
// if err != nil {
// return nil, err
// }
//}
vmenv := vm.NewEVM(vmctx, vm.TxContext{GasPrice: big.NewInt(0)}, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
statedb.SetLogger(tracer.Hooks)
// Define a meaningful timeout of a single transaction trace
if config.Timeout != nil {
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
return nil, err
}
}
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
go func() {
<-deadlineCtx.Done()
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
tracer.Stop(errors.New("execution timeout"))
// Stop evm execution. Note cancellation is not necessarily immediate.
vmenv.Cancel()
}
}()
defer cancel()
// Call Prepare to clear out the statedb access list
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
message.IsRip7560Frame = true
_, err = core.ApplyTransactionWithEVM(message, api.backend.ChainConfig(), new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, tx, &usedGas, vmenv)
if err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
}
return tracer.GetResult()
}

View file

@ -0,0 +1,70 @@
package native
import (
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
)
func init() {
tracers.DefaultDirectory.Register("rip7560Validation", newRip7560Tracer, false)
}
func newRip7560Tracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
var config prestateTracerConfig
if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err
}
}
t := &rip7560ValidationTracer{
TraceResults: make([]stateMap, 0),
UsedOpcodes: make([]map[byte]bool, 0),
Created: make([]map[common.Address]bool, 0),
Deleted: make([]map[common.Address]bool, 0),
}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnOpcode: t.OnOpcode,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
}
// Array fields contain of all access details of all validation frames
type rip7560ValidationTracer struct {
env *tracing.VMContext
TraceResults []stateMap `json:"traceResults"`
UsedOpcodes []map[byte]bool `json:"usedOpcodes"`
Created []map[common.Address]bool `json:"created"`
Deleted []map[common.Address]bool `json:"deleted"`
// todo
//interrupt atomic.Bool // Atomic flag to signal execution interruption
//reason error // Textual reason for the interruption
}
func (t *rip7560ValidationTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
}
func (t *rip7560ValidationTracer) OnTxEnd(receipt *types.Receipt, err error) {
}
func (t *rip7560ValidationTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
fmt.Printf("%s %d %d", vm.OpCode(opcode).String(), cost, depth)
}
func (t *rip7560ValidationTracer) GetResult() (json.RawMessage, error) {
jsonResult, err := json.MarshalIndent(*t, "", " ")
return jsonResult, err
}
func (t *rip7560ValidationTracer) Stop(err error) {
}

View file

@ -79,13 +79,13 @@ type TransactionArgs struct {
Sender *common.Address `json:"sender"`
Signature *hexutil.Bytes
Paymaster *common.Address `json:"paymaster,omitempty"`
PaymasterData *hexutil.Bytes `json:"paymasterData"`
Deployer *common.Address `json:"deployer,omitempty"`
DeployerData *hexutil.Bytes
PaymasterData *hexutil.Bytes `json:"paymasterData,omitempty"`
Deployer *common.Address `json:"factory,omitempty"`
DeployerData *hexutil.Bytes `json:"factoryData,omitempty"`
BuilderFee *hexutil.Big
ValidationGas *hexutil.Uint64
PaymasterGas *hexutil.Uint64
PostOpGas *hexutil.Uint64
ValidationGas *hexutil.Uint64 `json:"verificationGasLimit"`
PaymasterGas *hexutil.Uint64 `json:"paymasterVerificationGasLimit"`
PostOpGas *hexutil.Uint64 `json:"paymasterPostOpGasLimit"`
}
// from retrieves the transaction sender address.