diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 51b55ffdbb..e04f503aeb 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1022,6 +1022,9 @@ func APIs(backend Backend) []rpc.API { { Namespace: "debug", Service: NewAPI(backend), + }, { + Namespace: "debug", + Service: NewRip7560API(backend), }, } } diff --git a/eth/tracers/api_tracing_rip7560.go b/eth/tracers/api_tracing_rip7560.go new file mode 100644 index 0000000000..31d1b0a5c4 --- /dev/null +++ b/eth/tracers/api_tracing_rip7560.go @@ -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() +} diff --git a/eth/tracers/native/rip7560_validation.go b/eth/tracers/native/rip7560_validation.go new file mode 100644 index 0000000000..336fb8edee --- /dev/null +++ b/eth/tracers/native/rip7560_validation.go @@ -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) { +} diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index ae935bdef9..5683b0a2de 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -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.