cmd/evm: Add t8n server

This commit is contained in:
Mario Vega 2024-05-07 20:05:48 +00:00
parent 1a79f8fe58
commit cfd7e06e6b
6 changed files with 292 additions and 118 deletions

View file

@ -154,4 +154,13 @@ var (
Usage: "sets the verbosity level", Usage: "sets the verbosity level",
Value: 3, Value: 3,
} }
PortFlag = &cli.IntFlag{
Name: "port",
Usage: "port to listen on",
Value: 0,
}
UnixSocketFlag = &cli.StringFlag{
Name: "unix-socket",
Usage: "File path to the unix socket to use",
}
) )

View file

@ -89,7 +89,7 @@ func Transaction(ctx *cli.Context) error {
return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err)) return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err))
} }
// Decode the body of already signed transactions // Decode the body of already signed transactions
body = common.FromHex(inputData.TxRlp) body = inputData.TxRlp
} else { } else {
// Read input from file // Read input from file
inFile, err := os.Open(txStr) inFile, err := os.Open(txStr)

View file

@ -17,13 +17,20 @@
package t8ntool package t8ntool
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
"net"
"net/http"
"os" "os"
"os/signal"
"path/filepath" "path/filepath"
"strings"
"syscall"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
@ -74,21 +81,102 @@ var (
_ cli.ExitCoder = (*NumberedError)(nil) _ cli.ExitCoder = (*NumberedError)(nil)
) )
type tracerFn func(baseDir string) func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error)
type input struct { type input struct {
Alloc types.GenesisAlloc `json:"alloc,omitempty"` Alloc types.GenesisAlloc `json:"alloc,omitempty"`
Env *stEnv `json:"env,omitempty"` Env *stEnv `json:"env,omitempty"`
Txs []*txWithKey `json:"txs,omitempty"` Txs []*txWithKey `json:"txs,omitempty"`
TxRlp string `json:"txsRlp,omitempty"` TxRlp hexutil.Bytes `json:"txsRlp,omitempty"`
} }
func Transition(ctx *cli.Context) error { func (i *input) prestate() Prestate {
var getTracer = func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) { return nil, nil, nil } return Prestate{
Pre: i.Alloc,
Env: *i.Env,
}
}
baseDir, err := createBasedir(ctx) func (i *input) txIterator(chainConfig *params.ChainConfig) (txIterator, error) {
if len(i.TxRlp) > 0 {
// Decode the body of already signed transactions
return newRlpTxIterator(i.TxRlp), nil
}
// We may have to sign the transactions.
signer := types.LatestSignerForChainID(chainConfig.ChainID)
txs, err := signUnsignedTransactions(i.Txs, signer)
return newSliceTxIterator(txs), err
}
type stateReq struct {
Fork string `json:"fork,omitempty"`
ChainID int64 `json:"chainid,omitempty"`
Reward int64 `json:"reward,omitempty"`
}
type transitionRequest struct {
Input input `json:"input,omitempty"`
State stateReq `json:"state,omitempty"`
BaseDir string `json:"baseDir,omitempty"`
}
type transitionRequestOutput struct {
Result *ExecutionResult `json:"result"`
Alloc Alloc `json:"alloc"`
Body hexutil.Bytes `json:"body"`
}
func (r *transitionRequest) process(tracerGenFn tracerFn) (*transitionRequestOutput, error) {
baseDir, err := createBasedirFromString(r.BaseDir)
if err != nil { if err != nil {
return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) return nil, fmt.Errorf("failed creating output basedir: %v", err)
} }
prestate := r.Input.prestate()
vmConfig := vm.Config{}
// Construct the chainconfig
var chainConfig *params.ChainConfig
if cConf, extraEips, err := tests.GetChainConfig(r.State.Fork); err != nil {
return nil, fmt.Errorf("failed constructing chain configuration: %v", err)
} else {
chainConfig = cConf
vmConfig.ExtraEips = extraEips
}
// Set the chain id
chainConfig.ChainID = big.NewInt(r.State.ChainID)
txIt, err := r.Input.txIterator(chainConfig)
if err != nil {
return nil, fmt.Errorf("failed loading transactions: %v", err)
}
if err := applyLondonChecks(&prestate.Env, chainConfig); err != nil {
return nil, fmt.Errorf("failed applying London checks: %v", err)
}
if err := applyShanghaiChecks(&prestate.Env, chainConfig); err != nil {
return nil, fmt.Errorf("failed applying Shanghai checks: %v", err)
}
if err := applyMergeChecks(&prestate.Env, chainConfig); err != nil {
return nil, fmt.Errorf("failed applying Merge checks: %v", err)
}
if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil {
return nil, fmt.Errorf("failed applying Cancun checks: %v", err)
}
// Run the test and aggregate the result
s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, r.State.Reward, tracerGenFn(baseDir))
if err != nil {
return nil, fmt.Errorf("failed applying prestate: %v", err)
}
// Dump the execution result
collector := make(Alloc)
s.DumpToCollector(collector, nil)
return &transitionRequestOutput{
Result: result,
Alloc: collector,
Body: body,
}, nil
}
func tracerGenerator(ctx *cli.Context) tracerFn {
if ctx.Bool(TraceFlag.Name) { // JSON opcode tracing if ctx.Bool(TraceFlag.Name) { // JSON opcode tracing
// Configure the EVM logger // Configure the EVM logger
logConfig := &logger.Config{ logConfig := &logger.Config{
@ -97,13 +185,15 @@ func Transition(ctx *cli.Context) error {
EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name), EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name),
Debug: true, Debug: true,
} }
getTracer = func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) { enableCallFrames := ctx.Bool(TraceEnableCallFramesFlag.Name)
return func(baseDir string) func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) {
return func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) {
traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String()))) traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String())))
if err != nil { if err != nil {
return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
} }
var l *tracing.Hooks var l *tracing.Hooks
if ctx.Bool(TraceEnableCallFramesFlag.Name) { if enableCallFrames {
l = logger.NewJSONLoggerWithCallFrames(logConfig, traceFile) l = logger.NewJSONLoggerWithCallFrames(logConfig, traceFile)
} else { } else {
l = logger.NewJSONLogger(logConfig, traceFile) l = logger.NewJSONLogger(logConfig, traceFile)
@ -116,48 +206,64 @@ func Transition(ctx *cli.Context) error {
} }
return tracer, traceFile, nil return tracer, traceFile, nil
} }
}
} else if ctx.IsSet(TraceTracerFlag.Name) { } else if ctx.IsSet(TraceTracerFlag.Name) {
var config json.RawMessage var config json.RawMessage
if ctx.IsSet(TraceTracerConfigFlag.Name) { if ctx.IsSet(TraceTracerConfigFlag.Name) {
config = []byte(ctx.String(TraceTracerConfigFlag.Name)) config = []byte(ctx.String(TraceTracerConfigFlag.Name))
} }
getTracer = func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) { tracerStr := ctx.String(TraceTracerFlag.Name)
return func(baseDir string) func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) {
return func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) {
traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String()))) traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String())))
if err != nil { if err != nil {
return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
} }
tracer, err := tracers.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), nil, config) tracer, err := tracers.DefaultDirectory.New(tracerStr, nil, config)
if err != nil { if err != nil {
return nil, nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err)) return nil, nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err))
} }
return tracer, traceFile, nil return tracer, traceFile, nil
} }
} }
}
// Default to no tracing
return func(baseDir string) func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) {
return func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) {
return nil, nil, nil
}
}
}
func Transition(ctx *cli.Context) error {
// We need to load three things: alloc, env and transactions. May be either in // We need to load three things: alloc, env and transactions. May be either in
// stdin input or in files. // stdin input or in files.
// Check if anything needs to be read from stdin // Check if anything needs to be read from stdin
var ( var (
prestate Prestate
txIt txIterator // txs to apply
allocStr = ctx.String(InputAllocFlag.Name) allocStr = ctx.String(InputAllocFlag.Name)
envStr = ctx.String(InputEnvFlag.Name) envStr = ctx.String(InputEnvFlag.Name)
txStr = ctx.String(InputTxsFlag.Name) txStr = ctx.String(InputTxsFlag.Name)
inputData = &input{}
) )
request := transitionRequest{
BaseDir: ctx.String(OutputBasedir.Name),
State: stateReq{
Fork: ctx.String(ForknameFlag.Name),
ChainID: ctx.Int64(ChainIDFlag.Name),
Reward: ctx.Int64(RewardFlag.Name),
},
}
// Figure out the prestate alloc // Figure out the prestate alloc
if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector {
decoder := json.NewDecoder(os.Stdin) decoder := json.NewDecoder(os.Stdin)
if err := decoder.Decode(inputData); err != nil { if err := decoder.Decode(&request.Input); err != nil {
return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err)) return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err))
} }
} }
if allocStr != stdinSelector { if allocStr != stdinSelector {
if err := readFile(allocStr, "alloc", &inputData.Alloc); err != nil { if err := readFile(allocStr, "alloc", &request.Input.Alloc); err != nil {
return err return err
} }
} }
prestate.Pre = inputData.Alloc
// Set the block environment // Set the block environment
if envStr != stdinSelector { if envStr != stdinSelector {
@ -165,46 +271,116 @@ func Transition(ctx *cli.Context) error {
if err := readFile(envStr, "env", &env); err != nil { if err := readFile(envStr, "env", &env); err != nil {
return err return err
} }
inputData.Env = &env request.Input.Env = &env
} }
prestate.Env = *inputData.Env
vmConfig := vm.Config{} // Load the transactions from file if needed
// Construct the chainconfig if txStr != stdinSelector {
var chainConfig *params.ChainConfig data, err := os.ReadFile(txStr)
if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { if err != nil {
return NewError(ErrorConfig, fmt.Errorf("failed constructing chain configuration: %v", err)) return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err))
}
if strings.HasSuffix(txStr, ".rlp") { // A file containing an rlp list
err = json.Unmarshal(data, &request.Input.TxRlp)
} else { } else {
chainConfig = cConf err = json.Unmarshal(data, &request.Input.Txs)
vmConfig.ExtraEips = extraEips }
if err != nil {
return fmt.Errorf("failed unmarshalling txs-file: %v", err)
}
} }
// Set the chain id
chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name))
if txIt, err = loadTransactions(txStr, inputData, prestate.Env, chainConfig); err != nil { result, err := request.process(tracerGenerator(ctx))
return err
}
if err := applyLondonChecks(&prestate.Env, chainConfig); err != nil {
return err
}
if err := applyShanghaiChecks(&prestate.Env, chainConfig); err != nil {
return err
}
if err := applyMergeChecks(&prestate.Env, chainConfig); err != nil {
return err
}
if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil {
return err
}
// Run the test and aggregate the result
s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), getTracer)
if err != nil { if err != nil {
return err return err
} }
// Dump the execution result return dispatchOutput(ctx, request.BaseDir, result.Result, result.Alloc, result.Body)
collector := make(Alloc) }
s.DumpToCollector(collector, nil)
return dispatchOutput(ctx, baseDir, result, collector, body) func TransitionServer(ctx *cli.Context) error {
// Start the server
server := &transitionServer{
port: ctx.Int(PortFlag.Name),
unixSocket: ctx.String(UnixSocketFlag.Name),
tracerGenFn: tracerGenerator(ctx),
}
if err := server.start(); err != nil {
return NewError(ErrorIO, fmt.Errorf("failed starting server: %v", err))
}
log.Info("Started server", "port", server.port)
// Wait for a signal to shutdown
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-done
log.Info("Shutting down server")
if err := server.shutdown(); err != nil {
return NewError(ErrorIO, fmt.Errorf("failed shutting down server: %v", err))
}
return nil
}
type transitionServer struct {
port int
unixSocket string
tracerGenFn tracerFn
httpSrv *http.Server
}
func (server *transitionServer) start() error {
// start the HTTP listener
var (
listener net.Listener
err error
)
if server.unixSocket != "" {
listener, err = net.Listen("unix", server.unixSocket)
} else {
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", server.port))
}
if err != nil {
return err
}
// Bundle and start the HTTP server
server.httpSrv = &http.Server{
Handler: server,
}
go server.httpSrv.Serve(listener)
if server.unixSocket == "" {
server.port = listener.Addr().(*net.TCPAddr).Port
}
return err
}
func (server *transitionServer) shutdown() error {
if server.httpSrv == nil {
return nil
}
return server.httpSrv.Shutdown(context.Background())
}
func (server *transitionServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
reqStartTime := time.Now()
decoder := json.NewDecoder(req.Body)
var request transitionRequest
// Parse this from the http request
if err := decoder.Decode(&request); err != nil {
http.Error(res, fmt.Sprintf("failed unmarshalling request: %v", err), http.StatusBadRequest)
return
}
output, err := request.process(server.tracerGenFn)
if err != nil {
http.Error(res, fmt.Sprintf("failed processing request: %v", err), http.StatusInternalServerError)
return
}
// Write the http response
res.Header().Set("Content-Type", "application/json")
json.NewEncoder(res).Encode(output)
log.Info("Processed request", "duration", time.Since(reqStartTime))
} }
func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {

View file

@ -22,14 +22,10 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"os"
"strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -112,37 +108,6 @@ func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Tran
return signedTxs, nil return signedTxs, nil
} }
func loadTransactions(txStr string, inputData *input, env stEnv, chainConfig *params.ChainConfig) (txIterator, error) {
var txsWithKeys []*txWithKey
if txStr != stdinSelector {
data, err := os.ReadFile(txStr)
if err != nil {
return nil, NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err))
}
if strings.HasSuffix(txStr, ".rlp") { // A file containing an rlp list
var body hexutil.Bytes
if err := json.Unmarshal(data, &body); err != nil {
return nil, err
}
return newRlpTxIterator(body), nil
}
if err := json.Unmarshal(data, &txsWithKeys); err != nil {
return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshalling txs-file: %v", err))
}
} else {
if len(inputData.TxRlp) > 0 {
// Decode the body of already signed transactions
return newRlpTxIterator(common.FromHex(inputData.TxRlp)), nil
}
// JSON encoded transactions
txsWithKeys = inputData.Txs
}
// We may have to sign the transactions.
signer := types.LatestSignerForChainID(chainConfig.ChainID)
txs, err := signUnsignedTransactions(txsWithKeys, signer)
return newSliceTxIterator(txs), err
}
type txIterator interface { type txIterator interface {
// Next returns true until EOF // Next returns true until EOF
Next() bool Next() bool

View file

@ -40,15 +40,20 @@ func readFile(path, desc string, dest interface{}) error {
// createBasedir makes sure the basedir exists, if user specified one. // createBasedir makes sure the basedir exists, if user specified one.
func createBasedir(ctx *cli.Context) (string, error) { func createBasedir(ctx *cli.Context) (string, error) {
baseDir := ""
if ctx.IsSet(OutputBasedir.Name) { if ctx.IsSet(OutputBasedir.Name) {
if base := ctx.String(OutputBasedir.Name); len(base) > 0 { base := ctx.String(OutputBasedir.Name)
err := os.MkdirAll(base, 0755) // //rw-r--r-- return createBasedirFromString(base)
}
return "", nil
}
func createBasedirFromString(baseDirPath string) (string, error) {
if len(baseDirPath) > 0 {
err := os.MkdirAll(baseDirPath, 0755) // //rw-r--r--
if err != nil { if err != nil {
return "", err return "", err
} }
baseDir = base return baseDirPath, nil
} }
} return "", nil
return baseDir, nil
} }

View file

@ -166,6 +166,24 @@ var stateTransitionCommand = &cli.Command{
}, },
} }
var stateTransitionServerCommand = &cli.Command{
Name: "transition-server",
Aliases: []string{"t8n-server"},
Usage: "Instantiates a server that accepts requests for full state transition",
Action: t8ntool.TransitionServer,
Flags: []cli.Flag{
t8ntool.TraceFlag,
t8ntool.TraceTracerFlag,
t8ntool.TraceTracerConfigFlag,
t8ntool.TraceEnableMemoryFlag,
t8ntool.TraceDisableStackFlag,
t8ntool.TraceEnableReturnDataFlag,
t8ntool.TraceEnableCallFramesFlag,
t8ntool.PortFlag,
t8ntool.UnixSocketFlag,
},
}
var transactionCommand = &cli.Command{ var transactionCommand = &cli.Command{
Name: "transaction", Name: "transaction",
Aliases: []string{"t9n"}, Aliases: []string{"t9n"},
@ -233,6 +251,7 @@ func init() {
blockTestCommand, blockTestCommand,
stateTestCommand, stateTestCommand,
stateTransitionCommand, stateTransitionCommand,
stateTransitionServerCommand,
transactionCommand, transactionCommand,
blockBuilderCommand, blockBuilderCommand,
} }