Skip to content

Commit

Permalink
cannon: Multi VM executor (ethereum-optimism#12072)
Browse files Browse the repository at this point in the history
* cannon: Multi VM executor

* fix run subcmd arg fwding

* fix mt prestate

* add list subcmd; multicannon in op-stack-go

* remove cannon-latest

* safer strconv

* lint

* include .gitkeep in embed

* fix .git copy

* add detect.go tests

* add nosemgrep

* review comments

* list filtering

* add note to MIPS.sol in version stf ref

* use fork-exec

* minimal flag parsing

* load old cannon binaries from docker images

* note

* --help flag defaults

* remove redundant copy from cannon-builder-0
  • Loading branch information
Inphi authored Sep 25, 2024
1 parent 3072549 commit 56502dd
Show file tree
Hide file tree
Showing 21 changed files with 618 additions and 73 deletions.
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 --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
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 --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 --),
Expand Down
64 changes: 31 additions & 33 deletions cannon/cmd/load_elf.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ 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"
)

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",
Expand All @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
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
}

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)
}
}
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)
})
}
25 changes: 25 additions & 0 deletions cannon/mipsevm/versions/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
Expand Down Expand Up @@ -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")
}
}
Empty file.
Loading

0 comments on commit 56502dd

Please sign in to comment.