Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cannon: Multi VM executor #12072

Merged
merged 20 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 --version single --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 --version multi --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 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
Expand Down
1 change: 1 addition & 0 deletions cannon/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ state.json
*.pprof
*.out
bin
multicannon/embeds/cannon*
11 changes: 9 additions & 2 deletions cannon/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cannon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 --version single --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 --),
Expand Down
26 changes: 15 additions & 11 deletions cannon/cmd/load_elf.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,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)
48 changes: 26 additions & 22 deletions cannon/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
22 changes: 13 additions & 9 deletions cannon/cmd/witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
35 changes: 35 additions & 0 deletions cannon/mipsevm/versions/detect.go
Original file line number Diff line number Diff line change
@@ -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
}
Inphi marked this conversation as resolved.
Show resolved Hide resolved

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)
Inphi marked this conversation as resolved.
Show resolved Hide resolved
}
}
65 changes: 65 additions & 0 deletions cannon/mipsevm/versions/detect_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
11 changes: 11 additions & 0 deletions cannon/mipsevm/versions/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,14 @@ 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"
}
}
Empty file.
73 changes: 73 additions & 0 deletions cannon/multicannon/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"embed"
"errors"
"fmt"
"os"
"path/filepath"
"syscall"

"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(args []string, ver versions.StateVersion) error {
switch ver {
case versions.VersionSingleThreaded, versions.VersionMultiThreaded:
default:
return errors.New("unsupported verrsion")
Inphi marked this conversation as resolved.
Show resolved Hide resolved
}

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)
}

execArgs := append([]string{cannonProgramName}, args...)

// nosemgrep: go.lang.security.audit.dangerous-syscall-exec.dangerous-syscall-exec
if err := syscall.Exec(cannonProgramPath, execArgs, os.Environ()); err != nil {
Inphi marked this conversation as resolved.
Show resolved Hide resolved
fmt.Fprintf(os.Stderr, "Error executing %s: %v\n", cannonProgramName, err)
os.Exit(1)
}

panic("unreachable")
}

func extractTempFile(name string, data []byte) (string, error) {
tempDir := os.TempDir()
tempFile, err := os.CreateTemp(tempDir, name+"-*")
if err != nil {
Inphi marked this conversation as resolved.
Show resolved Hide resolved
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)
}
40 changes: 40 additions & 0 deletions cannon/multicannon/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"fmt"
"strconv"
"strings"

"github.com/urfave/cli/v2"

"github.com/ethereum-optimism/optimism/cannon/mipsevm/versions"
)

func List(ctx *cli.Context) error {
Inphi marked this conversation as resolved.
Show resolved Hide resolved
entries, err := vmFS.ReadDir(baseDir)
if err != nil {
return err
}
for _, entry := range entries {
filename := entry.Name()
toks := strings.Split(filename, "-")
if len(toks) != 2 {
fmt.Printf("filename: %s\tversion: %s\n", entry.Name(), "unknown")
continue
}
ver, err := strconv.ParseUint(toks[1], 10, 8)
if err != nil {
fmt.Printf("filename: %s\tversion: %s\n", entry.Name(), "unknown")
continue
}
fmt.Printf("filename: %s\tversion: %s\n", entry.Name(), versions.StateVersion(ver))
Fixed Show fixed Hide fixed
}
return nil
}

var ListCommand = &cli.Command{
Name: "list",
Usage: "List embedded Cannon VM implementations",
Description: "List embedded Cannon VM implementations",
Action: List,
}
Loading