Skip to content
Closed
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
9 changes: 4 additions & 5 deletions cannon/cmd/load_elf.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ func LoadELF(ctx *cli.Context) error {
if err != nil {
return err
}
switch ver {
case versions.GetCurrentSingleThreaded():
if versions.IsSupportedSingleThreaded(ver) {
createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
return program.LoadELF(f, singlethreaded.CreateInitialState)
}
Expand All @@ -72,11 +71,11 @@ func LoadELF(ctx *cli.Context) error {
}
return program.PatchStack(state)
}
case versions.GetCurrentMultiThreaded(), versions.GetCurrentMultiThreaded64():
} else if versions.IsSupportedMultiThreaded(ver) || versions.IsSupportedMultiThreaded64(ver) {
createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
return program.LoadELF(f, multithreaded.CreateInitialState)
}
default:
} else {
return fmt.Errorf("unsupported state version: %d (%s)", ver, ver.String())
}

Expand All @@ -97,7 +96,7 @@ func LoadELF(ctx *cli.Context) error {
}

// Ensure the state is written with appropriate version information
versionedState, err := versions.NewFromState(state)
versionedState, err := versions.NewFromState(ver, state)
if err != nil {
return fmt.Errorf("failed to create versioned state: %w", err)
}
Expand Down
10 changes: 9 additions & 1 deletion cannon/mipsevm/iface.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type FPVMState interface {
EncodeWitness() (witness []byte, hash common.Hash)

// CreateVM creates a FPVM that can operate on this state.
CreateVM(logger log.Logger, po PreimageOracle, stdOut, stdErr io.Writer, meta Metadata) FPVM
CreateVM(logger log.Logger, po PreimageOracle, stdOut, stdErr io.Writer, meta Metadata, features FeatureToggles) FPVM
}

type SymbolMatcher func(addr arch.Word) bool
Expand All @@ -68,6 +68,14 @@ type Metadata interface {
CreateSymbolMatcher(name string) SymbolMatcher
}

// FeatureToggles defines the set of features which are enabled only on some of the supported state versions.
// This allows supporting multiple state versions concurrently without needing to create completely separate
// FPVM implementations and duplicate a lot of code.
// Toggles here are temporary and should be removed once the newer state version is deployed widely. The older
// version can then be supported via multicannon pulling in a specific build and support for it dropped in latest code.
type FeatureToggles struct {
}

type FPVM interface {
// GetState returns the current state of the VM. The FPVMState is updated by successive calls to Step
GetState() FPVMState
Expand Down
4 changes: 3 additions & 1 deletion cannon/mipsevm/multithreaded/instrumented.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ type InstrumentedState struct {

preimageOracle *exec.TrackingPreimageOracleReader
meta mipsevm.Metadata
features mipsevm.FeatureToggles
}

var _ mipsevm.FPVM = (*InstrumentedState)(nil)

func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta mipsevm.Metadata) *InstrumentedState {
func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta mipsevm.Metadata, features mipsevm.FeatureToggles) *InstrumentedState {
return &InstrumentedState{
state: state,
log: log,
Expand All @@ -39,6 +40,7 @@ func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdEr
statsTracker: NoopStatsTracker(),
preimageOracle: exec.NewTrackingPreimageOracleReader(po),
meta: meta,
features: features,
}
}

Expand Down
21 changes: 17 additions & 4 deletions cannon/mipsevm/multithreaded/instrumented_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"io"
"os"
"reflect"
"testing"

"github.com/ethereum/go-ethereum/log"
Expand All @@ -16,7 +17,7 @@ import (
)

func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta *program.Metadata) mipsevm.FPVM {
return NewInstrumentedState(state, po, stdOut, stdErr, log, meta)
return NewInstrumentedState(state, po, stdOut, stdErr, log, meta, allFeaturesEnabled())
}

func TestInstrumentedState_OpenMips(t *testing.T) {
Expand Down Expand Up @@ -53,7 +54,7 @@ func TestInstrumentedState_UtilsCheck(t *testing.T) {
oracle := testutil.StaticOracle(t, []byte{})

var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta)
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta, allFeaturesEnabled())

for i := 0; i < 1_000_000; i++ {
if us.GetState().GetExited() {
Expand Down Expand Up @@ -186,7 +187,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
oracle := testutil.StaticOracle(t, []byte{})

var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta)
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta, allFeaturesEnabled())

for i := 0; i < test.steps; i++ {
if us.GetState().GetExited() {
Expand Down Expand Up @@ -231,7 +232,7 @@ func TestInstrumentedState_Alloc(t *testing.T) {
state, meta := testutil.LoadELFProgram(t, testutil.ProgramPath("alloc"), CreateInitialState, false)
oracle := testutil.AllocOracle(t, test.numAllocs, test.allocSize)

us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), meta)
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), meta, allFeaturesEnabled())
require.NoError(t, us.InitDebug())
// emulation shouldn't take more than 20 B steps
for i := 0; i < 20_000_000_000; i++ {
Expand All @@ -252,3 +253,15 @@ func TestInstrumentedState_Alloc(t *testing.T) {
})
}
}

// allFeaturesEnabled returns a FeatureToggles with all toggles enabled.
// We can't use versions.FeaturesForVersion to test the specific toggles for each version because it creates a
// dependency loop, so we just cover the everything enabled case as that should be the upcoming version.
func allFeaturesEnabled() mipsevm.FeatureToggles {
toggles := mipsevm.FeatureToggles{}
tRef := reflect.ValueOf(toggles)
for i := 0; i < tRef.NumField(); i++ {
tRef.Field(i).Set(reflect.ValueOf(true))
}
return toggles
}
4 changes: 2 additions & 2 deletions cannon/mipsevm/multithreaded/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ func CreateInitialState(pc, heapStart Word) *State {
return state
}

func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM {
func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata, features mipsevm.FeatureToggles) mipsevm.FPVM {
logger.Info("Using cannon multithreaded VM", "is32", arch.IsMips32)
return NewInstrumentedState(s, po, stdOut, stdErr, logger, meta)
return NewInstrumentedState(s, po, stdOut, stdErr, logger, meta, features)
}

func (s *State) GetCurrentThread() *ThreadState {
Expand Down
2 changes: 1 addition & 1 deletion cannon/mipsevm/program/testutil/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,6 @@ func (m MockFPVMState) EncodeWitness() (witness []byte, hash common.Hash) {
panic("not implemented")
}

func (m MockFPVMState) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM {
func (m MockFPVMState) CreateVM(_ log.Logger, _ mipsevm.PreimageOracle, _, _ io.Writer, _ mipsevm.Metadata, _ mipsevm.FeatureToggles) mipsevm.FPVM {
panic("not implemented")
}
2 changes: 1 addition & 1 deletion cannon/mipsevm/singlethreaded/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func CreateInitialState(pc, heapStart Word) *State {
return state
}

func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM {
func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata, _ mipsevm.FeatureToggles) mipsevm.FPVM {
return NewInstrumentedState(s, po, stdOut, stdErr, meta)
}

Expand Down
60 changes: 31 additions & 29 deletions cannon/mipsevm/tests/evm_common64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,36 +144,38 @@ func TestEVM_SingleStep_Shift64(t *testing.T) {
{name: "dsra32", funct: 0x3f, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x7F_FF_FF_FF_FF_FF_FF_FF), sa: 31, expectRes: Word(0x0)}, // dsra32 t8, s2, 1
}

v := GetMultiThreadedTestCase(t)
for i, tt := range cases {
testName := fmt.Sprintf("%v %v", v.Name, tt.name)
t.Run(testName, func(t *testing.T) {
pc := Word(0x0)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(pc))
state := goVm.GetState()
var insn uint32
var rtReg uint32
var rdReg uint32
rtReg = 18
rdReg = 8
insn = rtReg<<16 | rdReg<<11 | tt.sa<<6 | tt.funct
state.GetRegistersRef()[rdReg] = tt.rd
state.GetRegistersRef()[rtReg] = tt.rt
testutil.StoreInstruction(state.GetMemory(), pc, insn)
step := state.GetStep()

// Setup expectations
expected := testutil.NewExpectedState(state)
expected.ExpectStep()
expected.Registers[rdReg] = tt.expectRes

stepWitness, err := goVm.Step(true)
require.NoError(t, err)

// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
for _, v := range GetMultiThreadedTestCases(t) {
v := v
testName := fmt.Sprintf("%v %v", v.Name, tt.name)
t.Run(testName, func(t *testing.T) {
pc := Word(0x0)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(pc))
state := goVm.GetState()
var insn uint32
var rtReg uint32
var rdReg uint32
rtReg = 18
rdReg = 8
insn = rtReg<<16 | rdReg<<11 | tt.sa<<6 | tt.funct
state.GetRegistersRef()[rdReg] = tt.rd
state.GetRegistersRef()[rtReg] = tt.rt
testutil.StoreInstruction(state.GetMemory(), pc, insn)
step := state.GetStep()

// Setup expectations
expected := testutil.NewExpectedState(state)
expected.ExpectStep()
expected.Registers[rdReg] = tt.expectRes

stepWitness, err := goVm.Step(true)
require.NoError(t, err)

// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
}

Expand Down
13 changes: 3 additions & 10 deletions cannon/mipsevm/tests/evm_common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"
"time"

"github.com/ethereum-optimism/optimism/cannon/mipsevm/versions"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/stretchr/testify/require"

Expand All @@ -27,21 +28,13 @@ func TestEVM_OpenMIPS(t *testing.T) {
require.NoError(t, err)

cases := GetMipsVersionTestCases(t)
skippedTests := map[string][]string{
"multi-threaded": {"clone.bin"},
"single-threaded": {},
}

for _, c := range cases {
skipped, exists := skippedTests[c.Name]
require.True(t, exists)
for _, f := range testFiles {
testName := fmt.Sprintf("%v (%v)", f.Name(), c.Name)
t.Run(testName, func(t *testing.T) {
for _, skipped := range skipped {
if f.Name() == skipped {
t.Skipf("Skipping explicitly excluded open_mips testcase: %v", f.Name())
}
if !versions.IsSupportedSingleThreaded(c.Version) && f.Name() == "clone.bin" {
t.Skipf("%v only supported for singlethreaded VMs", f.Name())
}

oracle := testutil.SelectOracleFixture(t, f.Name())
Expand Down
35 changes: 22 additions & 13 deletions cannon/mipsevm/tests/evm_multithreaded64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,11 @@ var NoopSyscalls64 = map[string]uint32{
}

func TestEVM_NoopSyscall64(t *testing.T) {
testNoopSyscall(t, NoopSyscalls64)
t.Parallel()
for _, version := range GetMultiThreadedTestCases(t) {
noOpCalls := maps.Clone(NoopSyscalls64)
testNoopSyscall(t, version, noOpCalls)
}
}

func TestEVM_UnsupportedSyscall64(t *testing.T) {
Expand All @@ -480,7 +484,10 @@ func TestEVM_UnsupportedSyscall64(t *testing.T) {
unsupportedSyscalls = append(unsupportedSyscalls, candidate)
}

testUnsupportedSyscall(t, unsupportedSyscalls)
for _, version := range GetMultiThreadedTestCases(t) {
unsupported := unsupportedSyscalls
testUnsupportedSyscall(t, version, unsupported)
}
}

// Asserts the undefined syscall handling on cannon64 triggers a VM panic
Expand All @@ -492,16 +499,18 @@ func TestEVM_UndefinedSyscall(t *testing.T) {
"SysLlseek": arch.SysLlseek,
}
for name, val := range undefinedSyscalls {
t.Run(name, func(t *testing.T) {
t.Parallel()
goVm, state, contracts := setup(t, int(val), nil)
testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = Word(val) // Set syscall number
proofData := multiThreadedProofGenerator(t, state)

require.Panics(t, func() { _, _ = goVm.Step(true) })
errorMessage := "unimplemented syscall"
testutil.AssertEVMReverts(t, state, contracts, nil, proofData, testutil.CreateErrorStringMatcher(errorMessage))
})
for _, version := range GetMultiThreadedTestCases(t) {
t.Run(fmt.Sprintf("%v-%v", version.Name, name), func(t *testing.T) {
t.Parallel()
goVm, state, contracts := setupWithTestCase(t, version, int(val), nil)
testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = Word(val) // Set syscall number
proofData := multiThreadedProofGenerator(t, state)

require.Panics(t, func() { _, _ = goVm.Step(true) })
errorMessage := "unimplemented syscall"
testutil.AssertEVMReverts(t, state, contracts, nil, proofData, testutil.CreateErrorStringMatcher(errorMessage))
})
}
}
}
Loading