diff --git a/Makefile b/Makefile index 6b1abdd37f004..4f329a4241e2b 100644 --- a/Makefile +++ b/Makefile @@ -142,13 +142,13 @@ $(DEVNET_CANNON_PRESTATE_FILES): make cannon-prestate-mt cannon-prestate: op-program cannon ## Generates prestate using cannon and op-program - ./cannon/bin/cannon load-elf --path op-program/bin/op-program-client.elf --out op-program/bin/prestate.json --meta op-program/bin/meta.json + ./cannon/bin/cannon load-elf --type singlethreaded --path op-program/bin/op-program-client.elf --out op-program/bin/prestate.json --meta op-program/bin/meta.json ./cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input op-program/bin/prestate.json --meta op-program/bin/meta.json --proof-fmt 'op-program/bin/%d.json' --output "" mv op-program/bin/0.json op-program/bin/prestate-proof.json .PHONY: cannon-prestate cannon-prestate-mt: op-program cannon ## Generates prestate using cannon and op-program in the multithreaded cannon format - ./cannon/bin/cannon load-elf --type cannon-mt --path op-program/bin/op-program-client.elf --out op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json + ./cannon/bin/cannon load-elf --type multithreaded --path op-program/bin/op-program-client.elf --out op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json ./cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json --proof-fmt 'op-program/bin/%d-mt.json' --output "" mv op-program/bin/0-mt.json op-program/bin/prestate-proof-mt.json .PHONY: cannon-prestate-mt diff --git a/cannon/.gitignore b/cannon/.gitignore index c3e45199f0edd..68424370890f6 100644 --- a/cannon/.gitignore +++ b/cannon/.gitignore @@ -13,3 +13,4 @@ state.json *.pprof *.out bin +multicannon/embeds/cannon* diff --git a/cannon/Makefile b/cannon/Makefile index 0f3836fb62fbb..5b911d7bd40a8 100644 --- a/cannon/Makefile +++ b/cannon/Makefile @@ -13,8 +13,15 @@ ifeq ($(shell uname),Darwin) FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic endif -cannon: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon . +cannon-impl: + env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon-impl . + +cannon-embeds: cannon-impl + @cp bin/cannon-impl ./multicannon/embeds/cannon-0 + @cp bin/cannon-impl ./multicannon/embeds/cannon-1 + +cannon: cannon-embeds + env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon ./multicannon/ clean: rm -rf bin diff --git a/cannon/README.md b/cannon/README.md index e9e751ce2ffe3..a3b9171939010 100644 --- a/cannon/README.md +++ b/cannon/README.md @@ -30,7 +30,7 @@ make cannon # Transform MIPS op-program client binary into first VM state. # This outputs state.json (VM state) and meta.json (for debug symbols). -./bin/cannon load-elf --path=../op-program/bin/op-program-client.elf +./bin/cannon load-elf --type singlethreaded --path=../op-program/bin/op-program-client.elf # Run cannon emulator (with example inputs) # Note that the server-mode op-program command is passed into cannon (after the --), diff --git a/cannon/cmd/load_elf.go b/cannon/cmd/load_elf.go index a6b9e0e5897a1..816eb7c02e466 100644 --- a/cannon/cmd/load_elf.go +++ b/cannon/cmd/load_elf.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" "github.com/ethereum-optimism/optimism/cannon/serialize" + openum "github.com/ethereum-optimism/optimism/op-service/enum" "github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil" ) @@ -19,9 +20,8 @@ import ( var ( LoadELFVMTypeFlag = &cli.StringFlag{ Name: "type", - Usage: "VM type to create state for. Options are 'cannon' (default), 'cannon-mt'", - Value: "cannon", - Required: false, + Usage: "VM type to create state for. Valid options: " + openum.EnumString(stateVersions()), + Required: true, } LoadELFPathFlag = &cli.PathFlag{ Name: "path", @@ -43,21 +43,12 @@ var ( } ) -type VMType string - -var ( - cannonVMType VMType = "cannon" - mtVMType VMType = "cannon-mt" -) - -func vmTypeFromString(ctx *cli.Context) (VMType, error) { - if vmTypeStr := ctx.String(LoadELFVMTypeFlag.Name); vmTypeStr == string(cannonVMType) { - return cannonVMType, nil - } else if vmTypeStr == string(mtVMType) { - return mtVMType, nil - } else { - return "", fmt.Errorf("unknown VM type %q", vmTypeStr) +func stateVersions() []string { + vers := make([]string, len(versions.StateVersionTypes)) + for i, v := range versions.StateVersionTypes { + vers[i] = v.String() } + return vers } func LoadELF(ctx *cli.Context) error { @@ -73,9 +64,12 @@ func LoadELF(ctx *cli.Context) error { var createInitialState func(f *elf.File) (mipsevm.FPVMState, error) var patcher = program.PatchStack - if vmType, err := vmTypeFromString(ctx); err != nil { + ver, err := versions.ParseStateVersion(ctx.String(LoadELFVMTypeFlag.Name)) + if err != nil { return err - } else if vmType == cannonVMType { + } + switch ver { + case versions.VersionSingleThreaded: createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) { return program.LoadELF(f, singlethreaded.CreateInitialState) } @@ -86,12 +80,12 @@ func LoadELF(ctx *cli.Context) error { } return program.PatchStack(state) } - } else if vmType == mtVMType { + case versions.VersionMultiThreaded: createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) { return program.LoadELF(f, multithreaded.CreateInitialState) } - } else { - return fmt.Errorf("invalid VM type: %q", vmType) + default: + return fmt.Errorf("unsupported state version: %d (%s)", ver, ver.String()) } state, err := createInitialState(elfProgram) @@ -118,15 +112,19 @@ func LoadELF(ctx *cli.Context) error { return serialize.Write(ctx.Path(LoadELFOutFlag.Name), versionedState, OutFilePerm) } -var LoadELFCommand = &cli.Command{ - Name: "load-elf", - Usage: "Load ELF file into Cannon state", - Description: "Load ELF file into Cannon state", - Action: LoadELF, - Flags: []cli.Flag{ - LoadELFVMTypeFlag, - LoadELFPathFlag, - LoadELFOutFlag, - LoadELFMetaFlag, - }, +func CreateLoadELFCommand(action cli.ActionFunc) *cli.Command { + return &cli.Command{ + Name: "load-elf", + Usage: "Load ELF file into Cannon state", + Description: "Load ELF file into Cannon state", + Action: action, + Flags: []cli.Flag{ + LoadELFVMTypeFlag, + LoadELFPathFlag, + LoadELFOutFlag, + LoadELFMetaFlag, + }, + } } + +var LoadELFCommand = CreateLoadELFCommand(LoadELF) diff --git a/cannon/cmd/run.go b/cannon/cmd/run.go index 03836d087d988..21f4f7c298253 100644 --- a/cannon/cmd/run.go +++ b/cannon/cmd/run.go @@ -496,26 +496,30 @@ func Run(ctx *cli.Context) error { return nil } -var RunCommand = &cli.Command{ - Name: "run", - Usage: "Run VM step(s) and generate proof data to replicate onchain.", - Description: "Run VM step(s) and generate proof data to replicate onchain. See flags to match when to output a proof, a snapshot, or to stop early.", - Action: Run, - Flags: []cli.Flag{ - RunInputFlag, - RunOutputFlag, - RunProofAtFlag, - RunProofFmtFlag, - RunSnapshotAtFlag, - RunSnapshotFmtFlag, - RunStopAtFlag, - RunStopAtPreimageFlag, - RunStopAtPreimageTypeFlag, - RunStopAtPreimageLargerThanFlag, - RunMetaFlag, - RunInfoAtFlag, - RunPProfCPU, - RunDebugFlag, - RunDebugInfoFlag, - }, +func CreateRunCommand(action cli.ActionFunc) *cli.Command { + return &cli.Command{ + Name: "run", + Usage: "Run VM step(s) and generate proof data to replicate onchain.", + Description: "Run VM step(s) and generate proof data to replicate onchain. See flags to match when to output a proof, a snapshot, or to stop early.", + Action: action, + Flags: []cli.Flag{ + RunInputFlag, + RunOutputFlag, + RunProofAtFlag, + RunProofFmtFlag, + RunSnapshotAtFlag, + RunSnapshotFmtFlag, + RunStopAtFlag, + RunStopAtPreimageFlag, + RunStopAtPreimageTypeFlag, + RunStopAtPreimageLargerThanFlag, + RunMetaFlag, + RunInfoAtFlag, + RunPProfCPU, + RunDebugFlag, + RunDebugInfoFlag, + }, + } } + +var RunCommand = CreateRunCommand(Run) diff --git a/cannon/cmd/witness.go b/cannon/cmd/witness.go index a4f2e60ab6a6c..753438493f954 100644 --- a/cannon/cmd/witness.go +++ b/cannon/cmd/witness.go @@ -39,13 +39,17 @@ func Witness(ctx *cli.Context) error { return nil } -var WitnessCommand = &cli.Command{ - Name: "witness", - Usage: "Convert a Cannon JSON state into a binary witness", - Description: "Convert a Cannon JSON state into a binary witness. The hash of the witness is written to stdout", - Action: Witness, - Flags: []cli.Flag{ - WitnessInputFlag, - WitnessOutputFlag, - }, +func CreateWitnessCommand(action cli.ActionFunc) *cli.Command { + return &cli.Command{ + Name: "witness", + Usage: "Convert a Cannon JSON state into a binary witness", + Description: "Convert a Cannon JSON state into a binary witness. The hash of the witness is written to stdout", + Action: action, + Flags: []cli.Flag{ + WitnessInputFlag, + WitnessOutputFlag, + }, + } } + +var WitnessCommand = CreateWitnessCommand(Witness) diff --git a/cannon/mipsevm/versions/detect.go b/cannon/mipsevm/versions/detect.go new file mode 100644 index 0000000000000..ca4b9be9c51d5 --- /dev/null +++ b/cannon/mipsevm/versions/detect.go @@ -0,0 +1,35 @@ +package versions + +import ( + "fmt" + "io" + + "github.com/ethereum-optimism/optimism/cannon/serialize" + "github.com/ethereum-optimism/optimism/op-service/ioutil" +) + +func DetectVersion(path string) (StateVersion, error) { + if !serialize.IsBinaryFile(path) { + return VersionSingleThreaded, nil + } + + var f io.ReadCloser + f, err := ioutil.OpenDecompressed(path) + if err != nil { + return 0, fmt.Errorf("failed to open file %q: %w", path, err) + } + defer f.Close() + + var ver StateVersion + bin := serialize.NewBinaryReader(f) + if err := bin.ReadUInt(&ver); err != nil { + return 0, err + } + + switch ver { + case VersionSingleThreaded, VersionMultiThreaded: + return ver, nil + default: + return 0, fmt.Errorf("%w: %d", ErrUnknownVersion, ver) + } +} diff --git a/cannon/mipsevm/versions/detect_test.go b/cannon/mipsevm/versions/detect_test.go new file mode 100644 index 0000000000000..38a90f1786948 --- /dev/null +++ b/cannon/mipsevm/versions/detect_test.go @@ -0,0 +1,65 @@ +package versions + +import ( + "os" + "path/filepath" + "testing" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/stretchr/testify/require" +) + +func TestDetectVersion(t *testing.T) { + t.Run("SingleThreadedJSON", func(t *testing.T) { + state, err := NewFromState(singlethreaded.CreateEmptyState()) + require.NoError(t, err) + path := writeToFile(t, "state.json", state) + version, err := DetectVersion(path) + require.NoError(t, err) + require.Equal(t, VersionSingleThreaded, version) + }) + + t.Run("SingleThreadedBinary", func(t *testing.T) { + state, err := NewFromState(singlethreaded.CreateEmptyState()) + require.NoError(t, err) + path := writeToFile(t, "state.bin.gz", state) + version, err := DetectVersion(path) + require.NoError(t, err) + require.Equal(t, VersionSingleThreaded, version) + }) + + t.Run("MultiThreadedBinary", func(t *testing.T) { + state, err := NewFromState(multithreaded.CreateEmptyState()) + require.NoError(t, err) + path := writeToFile(t, "state.bin.gz", state) + version, err := DetectVersion(path) + require.NoError(t, err) + require.Equal(t, VersionMultiThreaded, version) + }) +} + +func TestDetectVersionInvalid(t *testing.T) { + t.Run("bad gzip", func(t *testing.T) { + dir := t.TempDir() + filename := "state.bin.gz" + path := filepath.Join(dir, filename) + require.NoError(t, os.WriteFile(path, []byte("ekans"), 0o644)) + + _, err := DetectVersion(path) + require.ErrorContains(t, err, "failed to open file") + }) + + t.Run("unknown version", func(t *testing.T) { + dir := t.TempDir() + filename := "state.bin.gz" + path := filepath.Join(dir, filename) + const badVersion = 0xFF + err := ioutil.WriteCompressedBytes(path, []byte{badVersion}, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) + require.NoError(t, err) + + _, err = DetectVersion(path) + require.ErrorIs(t, err, ErrUnknownVersion) + }) +} diff --git a/cannon/mipsevm/versions/state.go b/cannon/mipsevm/versions/state.go index fcf7b1864f382..afd2a94204b39 100644 --- a/cannon/mipsevm/versions/state.go +++ b/cannon/mipsevm/versions/state.go @@ -16,6 +16,7 @@ import ( type StateVersion uint8 const ( + // VersionSingleThreaded is the version of the Cannon STF found in op-contracts/v1.6.0 - https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.6.0/packages/contracts-bedrock/src/cannon/MIPS.sol VersionSingleThreaded StateVersion = iota VersionMultiThreaded ) @@ -25,6 +26,8 @@ var ( ErrJsonNotSupported = errors.New("json not supported") ) +var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded} + func LoadStateFromFile(path string) (*VersionedState, error) { if !serialize.IsBinaryFile(path) { // Always use singlethreaded for JSON states @@ -103,3 +106,25 @@ func (s *VersionedState) MarshalJSON() ([]byte, error) { } return json.Marshal(s.FPVMState) } + +func (s StateVersion) String() string { + switch s { + case VersionSingleThreaded: + return "singlethreaded" + case VersionMultiThreaded: + return "multithreaded" + default: + return "unknown" + } +} + +func ParseStateVersion(ver string) (StateVersion, error) { + switch ver { + case "singlethreaded": + return VersionSingleThreaded, nil + case "multithreaded": + return VersionMultiThreaded, nil + default: + return StateVersion(0), errors.New("unknown state version") + } +} diff --git a/cannon/multicannon/embeds/.gitkeep b/cannon/multicannon/embeds/.gitkeep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/cannon/multicannon/exec.go b/cannon/multicannon/exec.go new file mode 100644 index 0000000000000..1372c035f5604 --- /dev/null +++ b/cannon/multicannon/exec.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + "embed" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" +) + +// use the all directive to ensure the .gitkeep file is retained and avoid compiler errors + +//go:embed all:embeds +var vmFS embed.FS + +const baseDir = "embeds" + +func ExecuteCannon(ctx context.Context, args []string, ver versions.StateVersion) error { + switch ver { + case versions.VersionSingleThreaded, versions.VersionMultiThreaded: + default: + return errors.New("unsupported version") + } + + cannonProgramName := vmFilename(ver) + cannonProgramBin, err := vmFS.ReadFile(cannonProgramName) + if err != nil { + return err + } + cannonProgramPath, err := extractTempFile(filepath.Base(cannonProgramName), cannonProgramBin) + if err != nil { + fmt.Fprintf(os.Stderr, "Error extracting %s: %v\n", cannonProgramName, err) + os.Exit(1) + } + defer os.Remove(cannonProgramPath) + + if err := os.Chmod(cannonProgramPath, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Error setting execute permission for %s: %v\n", cannonProgramName, err) + os.Exit(1) + } + + // nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command + cmd := exec.CommandContext(ctx, cannonProgramPath, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Start() + if err != nil { + return fmt.Errorf("unable to launch cannon-impl program: %w", err) + } + if err := cmd.Wait(); err != nil { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + // relay exit code to the parent process + os.Exit(exitErr.ExitCode()) + } else { + return fmt.Errorf("failed to wait for cannon-impl program: %w", err) + } + } + return nil +} + +func extractTempFile(name string, data []byte) (string, error) { + tempDir := os.TempDir() + tempFile, err := os.CreateTemp(tempDir, name+"-*") + if err != nil { + return "", err + } + defer tempFile.Close() + + if _, err := tempFile.Write(data); err != nil { + return "", err + } + + return tempFile.Name(), nil +} + +func vmFilename(ver versions.StateVersion) string { + return fmt.Sprintf("%s/cannon-%d", baseDir, ver) +} diff --git a/cannon/multicannon/list.go b/cannon/multicannon/list.go new file mode 100644 index 0000000000000..6e9e8a68b65a9 --- /dev/null +++ b/cannon/multicannon/list.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "math" + "strconv" + "strings" + + "github.com/urfave/cli/v2" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" +) + +func List(ctx *cli.Context) error { + return list() +} + +func list() error { + fmt.Println("Available cannon versions:") + artifacts, err := getArtifacts() + if err != nil { + return err + } + for _, art := range artifacts { + if art.isValid() { + fmt.Printf("filename: %s\tversion: %s (%d)\n", art.filename, versions.StateVersion(art.ver), art.ver) + } else { + fmt.Printf("filename: %s\tversion: %s\n", art.filename, "unknown") + } + } + return nil +} + +func getArtifacts() ([]artifact, error) { + var ret []artifact + entries, err := vmFS.ReadDir(baseDir) + if err != nil { + return nil, err + } + for _, entry := range entries { + filename := entry.Name() + toks := strings.Split(filename, "-") + if len(toks) != 2 { + continue + } + if toks[0] != "cannon" { + continue + } + ver, err := strconv.ParseUint(toks[1], 10, 8) + if err != nil { + ret = append(ret, artifact{filename, math.MaxUint64}) + continue + } + ret = append(ret, artifact{filename, ver}) + } + return ret, nil +} + +type artifact struct { + filename string + ver uint64 +} + +func (a artifact) isValid() bool { + return a.ver != math.MaxUint64 +} + +var ListCommand = &cli.Command{ + Name: "list", + Usage: "List embedded Cannon VM implementations", + Description: "List embedded Cannon VM implementations", + Action: List, +} diff --git a/cannon/multicannon/load_elf.go b/cannon/multicannon/load_elf.go new file mode 100644 index 0000000000000..cbe1fda463039 --- /dev/null +++ b/cannon/multicannon/load_elf.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "os" + + "github.com/ethereum-optimism/optimism/cannon/cmd" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" + "github.com/urfave/cli/v2" +) + +func LoadELF(ctx *cli.Context) error { + if len(os.Args) == 2 && os.Args[2] == "--help" { + if err := list(); err != nil { + return err + } + fmt.Println("use `--type --help` to get more detailed help") + } + + typ, err := parseFlag(os.Args[1:], "--type") + if err != nil { + return err + } + ver, err := versions.ParseStateVersion(typ) + if err != nil { + return err + } + return ExecuteCannon(ctx.Context, os.Args[1:], ver) +} + +var LoadELFCommand = cmd.CreateLoadELFCommand(LoadELF) diff --git a/cannon/multicannon/main.go b/cannon/multicannon/main.go new file mode 100644 index 0000000000000..31dde280d8d0e --- /dev/null +++ b/cannon/multicannon/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + "github.com/urfave/cli/v2" +) + +func main() { + app := cli.NewApp() + app.Name = "multicannon" + app.Usage = "MIPS Fault Proof tool" + app.Description = "MIPS Fault Proof tool" + app.Commands = []*cli.Command{ + LoadELFCommand, + WitnessCommand, + RunCommand, + ListCommand, + } + ctx := ctxinterrupt.WithCancelOnInterrupt(context.Background()) + err := app.RunContext(ctx, os.Args) + if err != nil { + if errors.Is(err, ctx.Err()) { + _, _ = fmt.Fprintf(os.Stderr, "command interrupted") + os.Exit(130) + } else { + _, _ = fmt.Fprintf(os.Stderr, "error: %v", err) + os.Exit(1) + } + } +} diff --git a/cannon/multicannon/run.go b/cannon/multicannon/run.go new file mode 100644 index 0000000000000..532cf317fb21c --- /dev/null +++ b/cannon/multicannon/run.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" +) + +func Run(ctx *cli.Context) error { + fmt.Printf("args %v\n", os.Args[:]) + if len(os.Args) == 3 && os.Args[2] == "--help" { + if err := list(); err != nil { + return err + } + fmt.Println("use `--input --help` to get more detailed help") + } + + inputPath, err := parsePathFlag(os.Args[1:], "--input") + if err != nil { + return err + } + version, err := versions.DetectVersion(inputPath) + if err != nil { + return err + } + return ExecuteCannon(ctx.Context, os.Args[1:], version) +} + +// var RunCommand = cmd.CreateRunCommand(Run) +var RunCommand = &cli.Command{ + Name: "run", + Usage: "Run VM step(s) and generate proof data to replicate onchain.", + Description: "Run VM step(s) and generate proof data to replicate onchain. See flags to match when to output a proof, a snapshot, or to stop early.", + Action: Run, + SkipFlagParsing: true, +} diff --git a/cannon/multicannon/util.go b/cannon/multicannon/util.go new file mode 100644 index 0000000000000..ea484c6ce2d2f --- /dev/null +++ b/cannon/multicannon/util.go @@ -0,0 +1,37 @@ +package main + +import ( + "errors" + "fmt" + "os" + "strings" +) + +// parseFlag reads a flag argument. It assumes the flag has an argument +func parseFlag(args []string, flag string) (string, error) { + for i := 0; i < len(args); i++ { + arg := args[i] + if strings.HasPrefix(arg, flag) { + toks := strings.Split(arg, "=") + if len(toks) == 2 { + return toks[1], nil + } else if i+1 == len(args) { + return "", fmt.Errorf("flag needs an argument: %s", flag) + } else { + return args[i+1], nil + } + } + } + return "", fmt.Errorf("missing flag: %s", flag) +} + +func parsePathFlag(args []string, flag string) (string, error) { + path, err := parseFlag(args, flag) + if err != nil { + return "", err + } + if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { + return "", fmt.Errorf("file `%s` does not exist", path) + } + return path, nil +} diff --git a/cannon/multicannon/util_test.go b/cannon/multicannon/util_test.go new file mode 100644 index 0000000000000..9997b1315a8f1 --- /dev/null +++ b/cannon/multicannon/util_test.go @@ -0,0 +1,68 @@ +package main + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseFlag(t *testing.T) { + cases := []struct { + name string + args string + flag string + expect string + expectErr string + }{ + { + name: "bar=one", + args: "--foo --bar=one --baz", + flag: "--bar", + expect: "one", + }, + { + name: "bar one", + args: "--foo --bar one --baz", + flag: "--bar", + expect: "one", + }, + { + name: "bar one first flag", + args: "--bar one --foo two --baz three", + flag: "--bar", + expect: "one", + }, + { + name: "bar one last flag", + args: "--foo --baz --bar one", + flag: "--bar", + expect: "one", + }, + { + name: "non-existent flag", + args: "--foo one", + flag: "--bar", + expectErr: "missing flag", + }, + { + name: "empty args", + args: "", + flag: "--foo", + expectErr: "missing flag", + }, + } + for _, tt := range cases { + tt := tt + t.Run(tt.name, func(t *testing.T) { + args := strings.Split(tt.args, " ") + result, err := parseFlag(args, tt.flag) + if tt.expectErr != "" { + require.ErrorContains(t, err, tt.expectErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.expect, result) + } + }) + } +} diff --git a/cannon/multicannon/witness.go b/cannon/multicannon/witness.go new file mode 100644 index 0000000000000..077d0d3f1aed5 --- /dev/null +++ b/cannon/multicannon/witness.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" + + "github.com/ethereum-optimism/optimism/cannon/cmd" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" +) + +func Witness(ctx *cli.Context) error { + if len(os.Args) == 3 && os.Args[2] == "--help" { + if err := list(); err != nil { + return err + } + fmt.Println("use `--input --help` to get more detailed help") + } + + inputPath, err := parsePathFlag(os.Args[1:], "--input") + if err != nil { + return err + } + version, err := versions.DetectVersion(inputPath) + if err != nil { + return err + } + return ExecuteCannon(ctx.Context, os.Args[1:], version) +} + +var WitnessCommand = cmd.CreateWitnessCommand(Witness) diff --git a/op-program/Dockerfile.repro b/op-program/Dockerfile.repro index 12e52ec5b5a5c..57f65bb72b81e 100644 --- a/op-program/Dockerfile.repro +++ b/op-program/Dockerfile.repro @@ -35,8 +35,8 @@ RUN --mount=type=cache,target=/root/.cache/go-build cd op-program && make op-pro GOOS=linux GOARCH=mips GOMIPS=softfloat GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_PROGRAM_VERSION" # Run the op-program-client.elf binary directly through cannon's load-elf subcommand. -RUN /app/cannon/bin/cannon load-elf --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate.json --meta "" -RUN /app/cannon/bin/cannon load-elf --type cannon-mt --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate-mt.bin.gz --meta "" +RUN /app/cannon/bin/cannon load-elf --type singlethreaded --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate.json --meta "" +RUN /app/cannon/bin/cannon load-elf --type multithreaded --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate-mt.bin.gz --meta "" # Generate the prestate proof containing the absolute pre-state hash. RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate.json --meta "" --proof-fmt '/app/op-program/bin/%d.json' --output "" diff --git a/ops/docker/op-stack-go/Dockerfile b/ops/docker/op-stack-go/Dockerfile index a395968fd07a9..540abfeb04669 100644 --- a/ops/docker/op-stack-go/Dockerfile +++ b/ops/docker/op-stack-go/Dockerfile @@ -46,8 +46,16 @@ ARG TARGETARCH # Build the Go services, utilizing caches and share the many common packages. # The "id" defaults to the value of "target", the cache will thus be reused during this build. # "sharing" defaults to "shared", the cache will thus be available to other concurrent docker builds. + +# For now fetch the v1 cannon binary from the op-challenger image +#FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.1.0 AS cannon-builder-0 + FROM --platform=$BUILDPLATFORM builder AS cannon-builder -ARG CANNON_VERSION=v0.0.0 +# note: bump this CANNON_VERSION when the VM behavior changes +ARG CANNON_VERSION=v1.0.0 +# uncomment these lines once there's a new Cannon version available +#COPY --from=cannon-builder-0 /usr/local/bin/cannon ./cannon/multicannon/embeds/cannon-0 +#COPY --from=cannon-builder-0 /usr/local/bin/cannon ./cannon/multicannon/embeds/cannon-1 RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd cannon && make cannon \ GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$CANNON_VERSION" @@ -158,4 +166,4 @@ CMD ["op-supervisor"] FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS op-deployer-target COPY --from=op-deployer-builder /app/op-chain-ops/bin/op-deployer /usr/local/bin/ -CMD ["op-deployer"] \ No newline at end of file +CMD ["op-deployer"]