Commit d41e5886 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Support running multiple prestates the same game type in run-trace (#12443)

parent 1ac85caa
...@@ -4,11 +4,11 @@ import ( ...@@ -4,11 +4,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/url" "slices"
"strings"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/flags" "github.com/ethereum-optimism/optimism/op-challenger/flags"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/runner" "github.com/ethereum-optimism/optimism/op-challenger/runner"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/cliapp"
...@@ -16,8 +16,12 @@ import ( ...@@ -16,8 +16,12 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
func RunTrace(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, error) { var (
ErrUnknownTraceType = errors.New("unknown trace type")
ErrInvalidPrestateHash = errors.New("invalid prestate hash")
)
func RunTrace(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, error) {
logger, err := setupLogging(ctx) logger, err := setupLogging(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -31,36 +35,21 @@ func RunTrace(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, er ...@@ -31,36 +35,21 @@ func RunTrace(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, er
if err := cfg.Check(); err != nil { if err := cfg.Check(); err != nil {
return nil, err return nil, err
} }
if err := checkMTCannonFlags(ctx, cfg); err != nil { runConfigs, err := parseRunArgs(ctx.StringSlice(RunTraceRunFlag.Name))
if err != nil {
return nil, err return nil, err
} }
if len(runConfigs) == 0 {
var mtPrestate common.Hash // Default to running on-chain version of each enabled trace type
var mtPrestateURL *url.URL for _, traceType := range cfg.TraceTypes {
if ctx.IsSet(addMTCannonPrestateFlag.Name) { runConfigs = append(runConfigs, runner.RunConfig{TraceType: traceType})
mtPrestate = common.HexToHash(ctx.String(addMTCannonPrestateFlag.Name))
mtPrestateURL, err = url.Parse(ctx.String(addMTCannonPrestateURLFlag.Name))
if err != nil {
return nil, fmt.Errorf("invalid mt-cannon prestate url (%v): %w", ctx.String(addMTCannonPrestateFlag.Name), err)
} }
} }
return runner.NewRunner(logger, cfg, mtPrestate, mtPrestateURL), nil return runner.NewRunner(logger, cfg, runConfigs), nil
}
func checkMTCannonFlags(ctx *cli.Context, cfg *config.Config) error {
if ctx.IsSet(addMTCannonPrestateFlag.Name) || ctx.IsSet(addMTCannonPrestateURLFlag.Name) {
if ctx.IsSet(addMTCannonPrestateFlag.Name) != ctx.IsSet(addMTCannonPrestateURLFlag.Name) {
return fmt.Errorf("both flag %v and %v must be set when running MT-Cannon traces", addMTCannonPrestateURLFlag.Name, addMTCannonPrestateFlag.Name)
}
if cfg.Cannon == (vm.Config{}) {
return errors.New("required Cannon vm configuration for mt-cannon traces is missing")
}
}
return nil
} }
func runTraceFlags() []cli.Flag { func runTraceFlags() []cli.Flag {
return append(flags.Flags, addMTCannonPrestateFlag, addMTCannonPrestateURLFlag) return append(flags.Flags, RunTraceRunFlag)
} }
var RunTraceCommand = &cli.Command{ var RunTraceCommand = &cli.Command{
...@@ -72,14 +61,50 @@ var RunTraceCommand = &cli.Command{ ...@@ -72,14 +61,50 @@ var RunTraceCommand = &cli.Command{
} }
var ( var (
addMTCannonPrestateFlag = &cli.StringFlag{ RunTraceRunFlag = &cli.StringSliceFlag{
Name: "add-mt-cannon-prestate", Name: "run",
Usage: "Use this prestate to run MT-Cannon compatibility tests", Usage: "Specify a trace to run. Format is traceType/name/prestateHash where " +
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "ADD_MT_CANNON_PRESTATE"), "traceType is the trace type to use with the prestate (e.g cannon or asterisc-kona), " +
} "name is an arbitrary name for the prestate to use when reporting metrics and" +
addMTCannonPrestateURLFlag = &cli.StringFlag{ "prestateHash is the hex encoded absolute prestate commitment to use. " +
Name: "add-mt-cannon-prestate-url", "If name is omitted the trace type name is used." +
Usage: "Use this prestate URL to run MT-Cannon compatibility tests", "If the prestateHash is omitted, the absolute prestate hash used for new games on-chain.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "ADD_MT_CANNON_PRESTATE_URL"), EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "RUN"),
} }
) )
func parseRunArgs(args []string) ([]runner.RunConfig, error) {
cfgs := make([]runner.RunConfig, len(args))
for i, arg := range args {
cfg, err := parseRunArg(arg)
if err != nil {
return nil, err
}
cfgs[i] = cfg
}
return cfgs, nil
}
func parseRunArg(arg string) (runner.RunConfig, error) {
cfg := runner.RunConfig{}
opts := strings.SplitN(arg, "/", 3)
if len(opts) == 0 {
return runner.RunConfig{}, fmt.Errorf("invalid run config %q", arg)
}
cfg.TraceType = types.TraceType(opts[0])
if !slices.Contains(types.TraceTypes, cfg.TraceType) {
return runner.RunConfig{}, fmt.Errorf("%w %q for run config %q", ErrUnknownTraceType, opts[0], arg)
}
if len(opts) > 1 {
cfg.Name = opts[1]
} else {
cfg.Name = cfg.TraceType.String()
}
if len(opts) > 2 {
cfg.Prestate = common.HexToHash(opts[2])
if cfg.Prestate == (common.Hash{}) {
return runner.RunConfig{}, fmt.Errorf("%w %q for run config %q", ErrInvalidPrestateHash, opts[2], arg)
}
}
return cfg, nil
}
package main
import (
"strings"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/runner"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestParseRunArg(t *testing.T) {
tests := []struct {
arg string
expected runner.RunConfig
err error
}{
{arg: "unknown/test1/0x1234", err: ErrUnknownTraceType},
{arg: "cannon", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: types.TraceTypeCannon.String()}},
{arg: "asterisc", expected: runner.RunConfig{TraceType: types.TraceTypeAsterisc, Name: types.TraceTypeAsterisc.String()}},
{arg: "cannon/test1", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: "test1"}},
{arg: "cannon/test1/0x1234", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: "test1", Prestate: common.HexToHash("0x1234")}},
{arg: "cannon/test1/invalid", err: ErrInvalidPrestateHash},
}
for _, test := range tests {
test := test
// Slash characters in test names confuse some things that parse the output as it looks like a subtest
t.Run(strings.ReplaceAll(test.arg, "/", "_"), func(t *testing.T) {
actual, err := parseRunArg(test.arg)
require.ErrorIs(t, err, test.err)
require.Equal(t, test.expected, actual)
})
}
}
...@@ -60,28 +60,6 @@ func createTraceProvider( ...@@ -60,28 +60,6 @@ func createTraceProvider(
return nil, errors.New("invalid trace type") return nil, errors.New("invalid trace type")
} }
func createMTTraceProvider(
ctx context.Context,
logger log.Logger,
m vm.Metricer,
vmConfig vm.Config,
prestateHash common.Hash,
absolutePrestateBaseURL *url.URL,
localInputs utils.LocalGameInputs,
dir string,
) (types.TraceProvider, error) {
executor := vm.NewOpProgramServerExecutor(logger)
stateConverter := cannon.NewStateConverter(vmConfig)
prestateSource := prestates.NewMultiPrestateProvider(absolutePrestateBaseURL, filepath.Join(dir, "prestates"), stateConverter)
prestatePath, err := prestateSource.PrestatePath(ctx, prestateHash)
if err != nil {
return nil, fmt.Errorf("failed to get prestate %v: %w", prestateHash, err)
}
prestateProvider := vm.NewPrestateProvider(prestatePath, stateConverter)
return cannon.NewTraceProvider(logger, m, vmConfig, executor, prestateProvider, prestatePath, localInputs, dir, 42), nil
}
func getPrestate(ctx context.Context, prestateHash common.Hash, prestateBaseUrl *url.URL, prestatePath string, dataDir string, stateConverter vm.StateConverter) (string, error) { func getPrestate(ctx context.Context, prestateHash common.Hash, prestateBaseUrl *url.URL, prestatePath string, dataDir string, stateConverter vm.StateConverter) (string, error) {
prestateSource := prestates.NewPrestateSource( prestateSource := prestates.NewPrestateSource(
prestateBaseUrl, prestateBaseUrl,
......
...@@ -5,9 +5,9 @@ import ( ...@@ -5,9 +5,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
...@@ -29,8 +29,6 @@ import ( ...@@ -29,8 +29,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
const mtCannonType = "mt-cannon"
var ( var (
ErrUnexpectedStatusCode = errors.New("unexpected status code") ErrUnexpectedStatusCode = errors.New("unexpected status code")
) )
...@@ -45,12 +43,17 @@ type Metricer interface { ...@@ -45,12 +43,17 @@ type Metricer interface {
RecordSuccess(vmType string) RecordSuccess(vmType string)
} }
type RunConfig struct {
TraceType types.TraceType
Name string
Prestate common.Hash
}
type Runner struct { type Runner struct {
log log.Logger log log.Logger
cfg *config.Config cfg *config.Config
addMTCannonPrestate common.Hash runConfigs []RunConfig
addMTCannonPrestateURL *url.URL m Metricer
m Metricer
running atomic.Bool running atomic.Bool
ctx context.Context ctx context.Context
...@@ -59,13 +62,12 @@ type Runner struct { ...@@ -59,13 +62,12 @@ type Runner struct {
metricsSrv *httputil.HTTPServer metricsSrv *httputil.HTTPServer
} }
func NewRunner(logger log.Logger, cfg *config.Config, mtCannonPrestate common.Hash, mtCannonPrestateURL *url.URL) *Runner { func NewRunner(logger log.Logger, cfg *config.Config, runConfigs []RunConfig) *Runner {
return &Runner{ return &Runner{
log: logger, log: logger,
cfg: cfg, cfg: cfg,
addMTCannonPrestate: mtCannonPrestate, runConfigs: runConfigs,
addMTCannonPrestateURL: mtCannonPrestateURL, m: NewMetrics(),
m: NewMetrics(),
} }
} }
...@@ -91,21 +93,21 @@ func (r *Runner) Start(ctx context.Context) error { ...@@ -91,21 +93,21 @@ func (r *Runner) Start(ctx context.Context) error {
} }
caller := batching.NewMultiCaller(l1Client, batching.DefaultBatchSize) caller := batching.NewMultiCaller(l1Client, batching.DefaultBatchSize)
for _, traceType := range r.cfg.TraceTypes { for _, runConfig := range r.runConfigs {
r.wg.Add(1) r.wg.Add(1)
go r.loop(ctx, traceType, rollupClient, caller) go r.loop(ctx, runConfig, rollupClient, caller)
} }
r.log.Info("Runners started") r.log.Info("Runners started", "num", len(r.runConfigs))
return nil return nil
} }
func (r *Runner) loop(ctx context.Context, traceType types.TraceType, client *sources.RollupClient, caller *batching.MultiCaller) { func (r *Runner) loop(ctx context.Context, runConfig RunConfig, client *sources.RollupClient, caller *batching.MultiCaller) {
defer r.wg.Done() defer r.wg.Done()
t := time.NewTicker(1 * time.Minute) t := time.NewTicker(1 * time.Minute)
defer t.Stop() defer t.Stop()
for { for {
r.runAndRecordOnce(ctx, traceType, client, caller) r.runAndRecordOnce(ctx, runConfig, client, caller)
select { select {
case <-t.C: case <-t.C:
case <-ctx.Done(): case <-ctx.Done():
...@@ -114,80 +116,50 @@ func (r *Runner) loop(ctx context.Context, traceType types.TraceType, client *so ...@@ -114,80 +116,50 @@ func (r *Runner) loop(ctx context.Context, traceType types.TraceType, client *so
} }
} }
func (r *Runner) runAndRecordOnce(ctx context.Context, traceType types.TraceType, client *sources.RollupClient, caller *batching.MultiCaller) { func (r *Runner) runAndRecordOnce(ctx context.Context, runConfig RunConfig, client *sources.RollupClient, caller *batching.MultiCaller) {
recordError := func(err error, traceType string, m Metricer, log log.Logger) { recordError := func(err error, traceType string, m Metricer, log log.Logger) {
if errors.Is(err, ErrUnexpectedStatusCode) { if errors.Is(err, ErrUnexpectedStatusCode) {
log.Error("Incorrect status code", "type", traceType, "err", err) log.Error("Incorrect status code", "type", runConfig.Name, "err", err)
m.RecordInvalid(traceType) m.RecordInvalid(traceType)
} else if err != nil { } else if err != nil {
log.Error("Failed to run", "type", traceType, "err", err) log.Error("Failed to run", "type", runConfig.Name, "err", err)
m.RecordFailure(traceType) m.RecordFailure(traceType)
} else { } else {
log.Info("Successfully verified output root", "type", traceType) log.Info("Successfully verified output root", "type", runConfig.Name)
m.RecordSuccess(traceType) m.RecordSuccess(traceType)
} }
} }
prestateHash, err := r.getPrestateHash(ctx, traceType, caller) prestateHash := runConfig.Prestate
if err != nil { if prestateHash == (common.Hash{}) {
recordError(err, traceType.String(), r.m, r.log) hash, err := r.getPrestateHash(ctx, runConfig.TraceType, caller)
return if err != nil {
recordError(err, runConfig.Name, r.m, r.log)
return
}
prestateHash = hash
} }
localInputs, err := r.createGameInputs(ctx, client) localInputs, err := r.createGameInputs(ctx, client)
if err != nil { if err != nil {
recordError(err, traceType.String(), r.m, r.log) recordError(err, runConfig.Name, r.m, r.log)
return return
} }
inputsLogger := r.log.New("l1", localInputs.L1Head, "l2", localInputs.L2Head, "l2Block", localInputs.L2BlockNumber, "claim", localInputs.L2Claim) inputsLogger := r.log.New("l1", localInputs.L1Head, "l2", localInputs.L2Head, "l2Block", localInputs.L2BlockNumber, "claim", localInputs.L2Claim)
var wg sync.WaitGroup // Sanitize the directory name.
wg.Add(1) safeName := regexp.MustCompile("[^a-zA-Z0-9_-]").ReplaceAllString(runConfig.Name, "")
go func() { dir, err := r.prepDatadir(safeName)
defer wg.Done()
dir, err := r.prepDatadir(traceType.String())
if err != nil {
recordError(err, traceType.String(), r.m, r.log)
return
}
err = r.runOnce(ctx, inputsLogger.With("type", traceType), traceType, prestateHash, localInputs, dir)
recordError(err, traceType.String(), r.m, r.log)
}()
if traceType == types.TraceTypeCannon && r.addMTCannonPrestate != (common.Hash{}) && r.addMTCannonPrestateURL != nil {
wg.Add(1)
go func() {
defer wg.Done()
dir, err := r.prepDatadir(mtCannonType)
if err != nil {
recordError(err, mtCannonType, r.m, r.log)
return
}
logger := inputsLogger.With("type", mtCannonType)
err = r.runMTOnce(ctx, logger, localInputs, dir)
recordError(err, mtCannonType, r.m, r.log.With(mtCannonType, true))
}()
}
wg.Wait()
}
func (r *Runner) runOnce(ctx context.Context, logger log.Logger, traceType types.TraceType, prestateHash common.Hash, localInputs utils.LocalGameInputs, dir string) error {
provider, err := createTraceProvider(ctx, logger, metrics.NewVmMetrics(r.m, traceType.String()), r.cfg, prestateHash, traceType, localInputs, dir)
if err != nil {
return fmt.Errorf("failed to create trace provider: %w", err)
}
hash, err := provider.Get(ctx, types.RootPosition)
if err != nil { if err != nil {
return fmt.Errorf("failed to execute trace provider: %w", err) recordError(err, runConfig.Name, r.m, r.log)
} return
if hash[0] != mipsevm.VMStatusValid {
return fmt.Errorf("%w: %v", ErrUnexpectedStatusCode, hash)
} }
return nil err = r.runOnce(ctx, inputsLogger.With("type", runConfig.Name), runConfig.Name, runConfig.TraceType, prestateHash, localInputs, dir)
recordError(err, runConfig.Name, r.m, r.log)
} }
func (r *Runner) runMTOnce(ctx context.Context, logger log.Logger, localInputs utils.LocalGameInputs, dir string) error { func (r *Runner) runOnce(ctx context.Context, logger log.Logger, name string, traceType types.TraceType, prestateHash common.Hash, localInputs utils.LocalGameInputs, dir string) error {
provider, err := createMTTraceProvider(ctx, logger, metrics.NewVmMetrics(r.m, mtCannonType), r.cfg.Cannon, r.addMTCannonPrestate, r.addMTCannonPrestateURL, localInputs, dir) provider, err := createTraceProvider(ctx, logger, metrics.NewVmMetrics(r.m, name), r.cfg, prestateHash, traceType, localInputs, dir)
if err != nil { if err != nil {
return fmt.Errorf("failed to create trace provider: %w", err) return fmt.Errorf("failed to create trace provider: %w", err)
} }
...@@ -201,8 +173,8 @@ func (r *Runner) runMTOnce(ctx context.Context, logger log.Logger, localInputs u ...@@ -201,8 +173,8 @@ func (r *Runner) runMTOnce(ctx context.Context, logger log.Logger, localInputs u
return nil return nil
} }
func (r *Runner) prepDatadir(traceType string) (string, error) { func (r *Runner) prepDatadir(name string) (string, error) {
dir := filepath.Join(r.cfg.Datadir, traceType) dir := filepath.Join(r.cfg.Datadir, name)
if err := os.RemoveAll(dir); err != nil { if err := os.RemoveAll(dir); err != nil {
return "", fmt.Errorf("failed to remove old dir: %w", err) return "", fmt.Errorf("failed to remove old dir: %w", err)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment