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