diff --git a/.circleci/config.yml b/.circleci/config.yml index 695945ff11195..751fb2796924d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -265,7 +265,7 @@ jobs: - run: name: build Cannon example binaries command: make elf # only compile ELF binaries with Go, we do not have MIPS GCC for creating the debug-dumps. - working_directory: cannon/testdata/example + working_directory: cannon/testdata - run: name: Cannon Go lint command: | @@ -2148,7 +2148,7 @@ workflows: notify: true mentions: "@proofs-team" no_output_timeout: 60m - test_timeout: 75m + test_timeout: 90m resource_class: ethereum-optimism/latitude-fps-1 environment_overrides: | export OP_E2E_CANNON_ENABLED="true" diff --git a/cannon/.gitignore b/cannon/.gitignore index c9a7f170c14d1..d28a9310e2a85 100644 --- a/cannon/.gitignore +++ b/cannon/.gitignore @@ -5,9 +5,9 @@ cache venv .idea *.log -testdata/example/bin contracts/out *.pprof *.out bin +testdata/**/bin/ multicannon/embeds/cannon* diff --git a/cannon/Dockerfile.diff b/cannon/Dockerfile.diff index 67a82bffafe07..2c7b788090ee1 100644 --- a/cannon/Dockerfile.diff +++ b/cannon/Dockerfile.diff @@ -23,6 +23,7 @@ ARG GIT_DATE ARG TARGETOS TARGETARCH +# TODO(#14692): Update to test cannon-multithreaded64-4 when new VM stabilizes FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.1.0-alpha.4 AS cannon-v2 FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.4.0 AS cannon-multithreaded64-3 diff --git a/cannon/Makefile b/cannon/Makefile index 4efd7dde06b33..3c8052f288cee 100644 --- a/cannon/Makefile +++ b/cannon/Makefile @@ -39,6 +39,7 @@ cannon-embeds: cannon32-impl cannon64-impl @cp bin/cannon32-impl ./multicannon/embeds/cannon-5 # 64-bit multithreaded vm @cp bin/cannon64-impl ./multicannon/embeds/cannon-6 + @cp bin/cannon64-impl ./multicannon/embeds/cannon-7 cannon: cannon-embeds env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon ./multicannon/ @@ -46,8 +47,13 @@ cannon: cannon-embeds clean: rm -rf bin multicannon/embeds/cannon* -elf: - make -C ./testdata/example elf +elf: elf-go-122 elf-go-123 + +elf-go-122: + make -C ./testdata/go-1-22 elf + +elf-go-123: + make -C ./testdata/go-1-23 elf sanitize-program: mips-linux-gnu-objdump -d -j .text $$GUEST_PROGRAM > ./bin/dump.txt @@ -67,7 +73,7 @@ test: elf contract test64 test64: elf contract go test -tags=cannon64 -run '(TestEVM.*64|TestHelloEVM|TestClaimEVM)' ./mipsevm/tests -diff-%-cannon: cannon elf +diff-%-cannon: cannon elf-go-122 # Load an elf file to create a prestate, and check that both cannon versions generate the same prestate @VM=$*; \ echo "Running diff for VM type $${VM}"; \ @@ -78,8 +84,8 @@ diff-%-cannon: cannon elf echo "Loading 32-bit program"; \ ELF_SUFFIX="elf"; \ fi; \ - $$OTHER_CANNON load-elf --type $$VM --path ./testdata/example/bin/hello.$${ELF_SUFFIX} --out ./bin/prestate-other.bin.gz --meta ""; \ - ./bin/cannon load-elf --type $$VM --path ./testdata/example/bin/hello.$${ELF_SUFFIX} --out ./bin/prestate.bin.gz --meta ""; + $$OTHER_CANNON load-elf --type $$VM --path ./testdata/go-1-22/bin/hello.$${ELF_SUFFIX} --out ./bin/prestate-other.bin.gz --meta ""; \ + ./bin/cannon load-elf --type $$VM --path ./testdata/go-1-22/bin/hello.$${ELF_SUFFIX} --out ./bin/prestate.bin.gz --meta ""; @cmp ./bin/prestate-other.bin.gz ./bin/prestate.bin.gz; @if [ $$? -eq 0 ]; then \ echo "Generated identical prestates"; \ @@ -134,6 +140,9 @@ fuzz: cannon-embeds \ cannon \ clean \ + elf \ + elf-go-122 \ + elf-go-123 \ test \ lint \ fuzz \ diff --git a/cannon/README.md b/cannon/README.md index a5af5b2e7ece9..2e1e73fc1fc3d 100644 --- a/cannon/README.md +++ b/cannon/README.md @@ -80,11 +80,11 @@ The smart-contracts are integrated into the Optimism monorepo contracts: `mipsevm` is Go tooling to test the onchain MIPS implementation, and generate proof data. -## `example` +## `testdata` Example programs that can be run and proven with Cannon. Optional dependency, but required for `mipsevm` Go tests. -See [`testdata/example/Makefile`](./testdata/example/Makefile) for building the example MIPS binaries. +See [`testdata/Makefile`](testdata/Makefile) for building these MIPS binaries. ## License diff --git a/cannon/mipsevm/arch/arch32.go b/cannon/mipsevm/arch/arch32.go index 2d16ed3b84737..814979168147d 100644 --- a/cannon/mipsevm/arch/arch32.go +++ b/cannon/mipsevm/arch/arch32.go @@ -74,6 +74,7 @@ const ( SysTgkill = 4266 SysGetRLimit = 4076 SysLseek = 4019 + SysEventFd2 = 4325 // Profiling-related syscalls SysSetITimer = 4104 SysTimerCreate = 4257 diff --git a/cannon/mipsevm/arch/arch64.go b/cannon/mipsevm/arch/arch64.go index 9c70fb7ee8336..49c93d8de9cf9 100644 --- a/cannon/mipsevm/arch/arch64.go +++ b/cannon/mipsevm/arch/arch64.go @@ -82,6 +82,7 @@ const ( SysTgkill = 5225 SysGetRLimit = 5095 SysLseek = 5008 + SysEventFd2 = 5284 // Profiling-related syscalls SysSetITimer = 5036 SysTimerCreate = 5216 diff --git a/cannon/mipsevm/iface.go b/cannon/mipsevm/iface.go index ed65960e570be..ad06cdceb6dc2 100644 --- a/cannon/mipsevm/iface.go +++ b/cannon/mipsevm/iface.go @@ -74,6 +74,7 @@ type Metadata interface { // 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 { + SupportNoopSysEventFd2 bool } type FPVM interface { diff --git a/cannon/mipsevm/multithreaded/instrumented_test.go b/cannon/mipsevm/multithreaded/instrumented_test.go index 0e650ec250203..9d619cea2d00a 100644 --- a/cannon/mipsevm/multithreaded/instrumented_test.go +++ b/cannon/mipsevm/multithreaded/instrumented_test.go @@ -37,6 +37,7 @@ func TestInstrumentedState_Claim(t *testing.T) { func TestInstrumentedState_UtilsCheck(t *testing.T) { // Sanity check that test running utilities will return a non-zero exit code on failure + testutil.Cannon64OnlyTest(t, "32-bit Cannon is deprecated") t.Parallel() cases := []struct { name string @@ -75,6 +76,7 @@ func TestInstrumentedState_UtilsCheck(t *testing.T) { } func TestInstrumentedState_MultithreadedProgram(t *testing.T) { + testutil.Cannon64OnlyTest(t, "32-bit Cannon is deprecated") if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") } @@ -208,6 +210,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { } } func TestInstrumentedState_Alloc(t *testing.T) { + testutil.Cannon64OnlyTest(t, "32-bit Cannon is deprecated") if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") } @@ -259,9 +262,14 @@ func TestInstrumentedState_Alloc(t *testing.T) { // 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) + tRef := reflect.ValueOf(&toggles).Elem() // Get a pointer and then dereference + for i := 0; i < tRef.NumField(); i++ { - tRef.Field(i).Set(reflect.ValueOf(true)) + field := tRef.Field(i) + if field.Kind() == reflect.Bool && field.CanSet() { + field.SetBool(true) + } } + return toggles } diff --git a/cannon/mipsevm/multithreaded/mips.go b/cannon/mipsevm/multithreaded/mips.go index cc3bdf2c52bf5..332a0abe40148 100644 --- a/cannon/mipsevm/multithreaded/mips.go +++ b/cannon/mipsevm/multithreaded/mips.go @@ -189,13 +189,16 @@ func (m *InstrumentedState) handleSyscall() error { case arch.SysTimerDelete: case arch.SysGetRLimit: case arch.SysLseek: + case arch.SysEventFd2: + if !m.features.SupportNoopSysEventFd2 { + m.handleUnrecognizedSyscall(syscallNum) + } default: // These syscalls have the same values on 64-bit. So we use if-stmts here to avoid "duplicate case" compiler error for the cannon64 build if arch.IsMips32 && (syscallNum == arch.SysFstat64 || syscallNum == arch.SysStat64 || syscallNum == arch.SysLlseek) { // noop } else { - m.Traceback() - panic(fmt.Sprintf("unrecognized syscall: %d", syscallNum)) + m.handleUnrecognizedSyscall(syscallNum) } } @@ -203,6 +206,11 @@ func (m *InstrumentedState) handleSyscall() error { return nil } +func (m *InstrumentedState) handleUnrecognizedSyscall(syscallNum Word) { + m.Traceback() + panic(fmt.Sprintf("unrecognized syscall: %d", syscallNum)) +} + func (m *InstrumentedState) syscallYield(thread *ThreadState) { v0 := Word(0) v1 := Word(0) diff --git a/cannon/mipsevm/multithreaded/state_test.go b/cannon/mipsevm/multithreaded/state_test.go index 2badb9ee1f6f7..1910e59872eee 100644 --- a/cannon/mipsevm/multithreaded/state_test.go +++ b/cannon/mipsevm/multithreaded/state_test.go @@ -101,7 +101,7 @@ func TestState_EncodeWitness(t *testing.T) { } func TestState_JSONCodec(t *testing.T) { - elfProgram, err := elf.Open("../../testdata/example/bin/hello.elf") + elfProgram, err := elf.Open("../../testdata/go-1-23/bin/hello.elf") require.NoError(t, err, "open ELF file") state, err := program.LoadELF(elfProgram, CreateInitialState) require.NoError(t, err, "load ELF into state") @@ -138,7 +138,7 @@ func TestState_JSONCodec(t *testing.T) { } func TestState_Binary(t *testing.T) { - elfProgram, err := elf.Open("../../testdata/example/bin/hello.elf") + elfProgram, err := elf.Open("../../testdata/go-1-23/bin/hello.elf") require.NoError(t, err, "open ELF file") state, err := program.LoadELF(elfProgram, CreateInitialState) require.NoError(t, err, "load ELF into state") diff --git a/cannon/mipsevm/singlethreaded/instrumented_test.go b/cannon/mipsevm/singlethreaded/instrumented_test.go index f1dfc8fae4dbe..c5a2a3efab76e 100644 --- a/cannon/mipsevm/singlethreaded/instrumented_test.go +++ b/cannon/mipsevm/singlethreaded/instrumented_test.go @@ -21,11 +21,3 @@ func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer func TestInstrumentedState_OpenMips(t *testing.T) { testutil.RunVMTests_OpenMips(t, CreateEmptyState, vmFactory) } - -func TestInstrumentedState_Hello(t *testing.T) { - testutil.RunVMTest_Hello(t, CreateInitialState, vmFactory, true) -} - -func TestInstrumentedState_Claim(t *testing.T) { - testutil.RunVMTest_Claim(t, CreateInitialState, vmFactory, true) -} diff --git a/cannon/mipsevm/singlethreaded/state_test.go b/cannon/mipsevm/singlethreaded/state_test.go index be2ce50357db4..7b89d536e9646 100644 --- a/cannon/mipsevm/singlethreaded/state_test.go +++ b/cannon/mipsevm/singlethreaded/state_test.go @@ -65,7 +65,7 @@ func TestStateHash(t *testing.T) { } func TestStateJSONCodec(t *testing.T) { - elfProgram, err := elf.Open("../../testdata/example/bin/hello.elf") + elfProgram, err := elf.Open("../../testdata/go-1-23/bin/hello.elf") require.NoError(t, err, "open ELF file") state, err := program.LoadELF(elfProgram, CreateInitialState) require.NoError(t, err, "load ELF into state") @@ -88,7 +88,7 @@ func TestStateJSONCodec(t *testing.T) { } func TestStateBinaryCodec(t *testing.T) { - elfProgram, err := elf.Open("../../testdata/example/bin/hello.elf") + elfProgram, err := elf.Open("../../testdata/go-1-23/bin/hello.elf") require.NoError(t, err, "open ELF file") state, err := program.LoadELF(elfProgram, CreateInitialState) require.NoError(t, err, "load ELF into state") diff --git a/cannon/mipsevm/tests/evm_common_test.go b/cannon/mipsevm/tests/evm_common_test.go index af0aa192409de..6aed714ff4884 100644 --- a/cannon/mipsevm/tests/evm_common_test.go +++ b/cannon/mipsevm/tests/evm_common_test.go @@ -1009,6 +1009,7 @@ func TestEVM_Fault(t *testing.T) { } func TestEVM_HelloProgram(t *testing.T) { + testutil.Cannon64OnlyTest(t, "32-bit Cannon is deprecated") if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") } @@ -1028,7 +1029,7 @@ func TestEVM_HelloProgram(t *testing.T) { state := goVm.GetState() start := time.Now() - for i := 0; i < 430_000; i++ { + for i := 0; i < 450_000; i++ { step := goVm.GetState().GetStep() if goVm.GetState().GetExited() { break @@ -1056,6 +1057,7 @@ func TestEVM_HelloProgram(t *testing.T) { } func TestEVM_ClaimProgram(t *testing.T) { + testutil.Cannon64OnlyTest(t, "32-bit Cannon is deprecated") if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") } @@ -1102,6 +1104,7 @@ func TestEVM_ClaimProgram(t *testing.T) { } func TestEVM_EntryProgram(t *testing.T) { + testutil.Cannon64OnlyTest(t, "32-bit Cannon is deprecated") if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") } diff --git a/cannon/mipsevm/tests/evm_multithreaded64_test.go b/cannon/mipsevm/tests/evm_multithreaded64_test.go index 713393f1aac33..d191aff9aa78a 100644 --- a/cannon/mipsevm/tests/evm_multithreaded64_test.go +++ b/cannon/mipsevm/tests/evm_multithreaded64_test.go @@ -7,6 +7,7 @@ package tests import ( "encoding/binary" "fmt" + "os" "slices" "testing" @@ -17,6 +18,7 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" ) func TestEVM_MT64_LL(t *testing.T) { @@ -43,49 +45,53 @@ func TestEVM_MT64_LL(t *testing.T) { {name: "4-byte-aligned addr, addr signed extended w overflow", base: 0x1000_0001, offset: 0xFF03, addr: 0x0000_0000_0FFF_FF04, memVal: memVal, retVal: 0x55667788, retReg: 5}, {name: "Return register set to 0", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retVal: 0x11223344, retReg: 0}, } - for i, c := range cases { - for _, withExistingReservation := range []bool{true, false} { - tName := fmt.Sprintf("%v (withExistingReservation = %v)", c.name, withExistingReservation) - t.Run(tName, func(t *testing.T) { - effAddr := arch.AddressMask & c.addr - - retReg := c.retReg - baseReg := 6 - insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (retReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x40)) - step := state.GetStep() - - // Set up state - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) - state.GetMemory().SetWord(effAddr, c.memVal) - state.GetRegistersRef()[baseReg] = c.base - if withExistingReservation { - state.LLReservationStatus = multithreaded.LLStatusActive32bit - state.LLAddress = c.addr + 1 - state.LLOwnerThread = 123 - } else { - state.LLReservationStatus = multithreaded.LLStatusNone - state.LLAddress = 0 - state.LLOwnerThread = 0 - } - - // Set up expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.LLReservationStatus = multithreaded.LLStatusActive32bit - expected.LLAddress = c.addr - expected.LLOwnerThread = state.GetCurrentThread().ThreadId - if retReg != 0 { - expected.ActiveThread().Registers[retReg] = c.retVal - } - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + versions := GetMultiThreadedTestCases(t) + for _, v := range versions { + for i, c := range cases { + for _, withExistingReservation := range []bool{true, false} { + tName := fmt.Sprintf("%v (vm = %v, withExistingReservation = %v)", c.name, v.Name, withExistingReservation) + t.Run(tName, func(t *testing.T) { + effAddr := arch.AddressMask & c.addr + + retReg := c.retReg + baseReg := 6 + insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (retReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0x40)) + state := mttestutil.GetMtState(t, goVm) + step := state.GetStep() + + // Set up state + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetMemory().SetWord(effAddr, c.memVal) + state.GetRegistersRef()[baseReg] = c.base + if withExistingReservation { + state.LLReservationStatus = multithreaded.LLStatusActive32bit + state.LLAddress = c.addr + 1 + state.LLOwnerThread = 123 + } else { + state.LLReservationStatus = multithreaded.LLStatusNone + state.LLAddress = 0 + state.LLOwnerThread = 0 + } + + // Set up expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.LLReservationStatus = multithreaded.LLStatusActive32bit + expected.LLAddress = c.addr + expected.LLOwnerThread = state.GetCurrentThread().ThreadId + if retReg != 0 { + expected.ActiveThread().Registers[retReg] = c.retVal + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), v.Contracts) + }) + } } } } @@ -127,66 +133,70 @@ func TestEVM_MT64_SC(t *testing.T) { {name: "Return register set to 0", base: 0x01, offset: 0x0138, addr: 0x0139, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 0, threadId: 4}, {name: "Zero valued ll args", base: 0x0, offset: 0x0, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 0}, } - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - effAddr := arch.AddressMask & c.addr - - // Setup - rtReg := c.rtReg - baseReg := 6 - insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil) - mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40)) - step := state.GetStep() - - // Define LL-related params - var llAddress, llOwnerThread Word - if v.matchAddr { - llAddress = c.addr - } else { - llAddress = c.addr + 1 - } - if v.matchThreadId { - llOwnerThread = c.threadId - } else { - llOwnerThread = c.threadId + 1 - } - - // Setup state - state.GetCurrentThread().ThreadId = c.threadId - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) - state.GetRegistersRef()[baseReg] = c.base - state.GetRegistersRef()[rtReg] = c.value - state.LLReservationStatus = v.llReservationStatus - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread - - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - var retVal Word - if v.shouldSucceed { - retVal = 1 - expected.ExpectMemoryWordWrite(effAddr, c.expectedMemVal) - expected.LLReservationStatus = multithreaded.LLStatusNone - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } else { - retVal = 0 - } - if rtReg != 0 { - expected.ActiveThread().Registers[rtReg] = retVal - } - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + versions := GetMultiThreadedTestCases(t) + for _, ver := range versions { + for i, c := range cases { + for _, llVar := range llVariations { + tName := fmt.Sprintf("%v (%v, %v)", c.name, ver.Name, llVar.name) + t.Run(tName, func(t *testing.T) { + effAddr := arch.AddressMask & c.addr + + // Setup + rtReg := c.rtReg + baseReg := 6 + insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40)) + step := state.GetStep() + + // Define LL-related params + var llAddress, llOwnerThread Word + if llVar.matchAddr { + llAddress = c.addr + } else { + llAddress = c.addr + 1 + } + if llVar.matchThreadId { + llOwnerThread = c.threadId + } else { + llOwnerThread = c.threadId + 1 + } + + // Setup state + state.GetCurrentThread().ThreadId = c.threadId + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[baseReg] = c.base + state.GetRegistersRef()[rtReg] = c.value + state.LLReservationStatus = llVar.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + var retVal Word + if llVar.shouldSucceed { + retVal = 1 + expected.ExpectMemoryWordWrite(effAddr, c.expectedMemVal) + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } else { + retVal = 0 + } + if rtReg != 0 { + expected.ActiveThread().Registers[rtReg] = retVal + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } } @@ -215,49 +225,53 @@ func TestEVM_MT64_LLD(t *testing.T) { {name: "Aligned addr, signed extended w overflow", base: 0x1000_0001, offset: 0xFF07, addr: 0x0000_0000_0FFF_FF08, memVal: memVal, retReg: 5}, {name: "Return register set to 0", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retReg: 0}, } - for i, c := range cases { - for _, withExistingReservation := range []bool{true, false} { - tName := fmt.Sprintf("%v (withExistingReservation = %v)", c.name, withExistingReservation) - t.Run(tName, func(t *testing.T) { - effAddr := arch.AddressMask & c.addr - - retReg := c.retReg - baseReg := 6 - insn := uint32((0b11_0100 << 26) | (baseReg & 0x1F << 21) | (retReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x40)) - step := state.GetStep() - - // Set up state - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) - state.GetMemory().SetWord(effAddr, c.memVal) - state.GetRegistersRef()[baseReg] = c.base - if withExistingReservation { - state.LLReservationStatus = multithreaded.LLStatusActive64bit - state.LLAddress = c.addr + 1 - state.LLOwnerThread = 123 - } else { - state.LLReservationStatus = multithreaded.LLStatusNone - state.LLAddress = 0 - state.LLOwnerThread = 0 - } - - // Set up expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.LLReservationStatus = multithreaded.LLStatusActive64bit - expected.LLAddress = c.addr - expected.LLOwnerThread = state.GetCurrentThread().ThreadId - if retReg != 0 { - expected.ActiveThread().Registers[retReg] = c.memVal - } - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + versions := GetMultiThreadedTestCases(t) + for _, v := range versions { + for i, c := range cases { + for _, withExistingReservation := range []bool{true, false} { + tName := fmt.Sprintf("%v (vm = %v, withExistingReservation = %v)", c.name, v.Name, withExistingReservation) + t.Run(tName, func(t *testing.T) { + effAddr := arch.AddressMask & c.addr + + retReg := c.retReg + baseReg := 6 + insn := uint32((0b11_0100 << 26) | (baseReg & 0x1F << 21) | (retReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0x40)) + state := mttestutil.GetMtState(t, goVm) + step := state.GetStep() + + // Set up state + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetMemory().SetWord(effAddr, c.memVal) + state.GetRegistersRef()[baseReg] = c.base + if withExistingReservation { + state.LLReservationStatus = multithreaded.LLStatusActive64bit + state.LLAddress = c.addr + 1 + state.LLOwnerThread = 123 + } else { + state.LLReservationStatus = multithreaded.LLStatusNone + state.LLAddress = 0 + state.LLOwnerThread = 0 + } + + // Set up expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.LLReservationStatus = multithreaded.LLStatusActive64bit + expected.LLAddress = c.addr + expected.LLOwnerThread = state.GetCurrentThread().ThreadId + if retReg != 0 { + expected.ActiveThread().Registers[retReg] = c.memVal + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), v.Contracts) + }) + } } } } @@ -300,66 +314,70 @@ func TestEVM_MT64_SCD(t *testing.T) { {name: "Return register set to 0", base: 0x01, offset: 0x0138, addr: 0x0139, rtReg: 0, threadId: 4}, {name: "Zero valued ll args", base: 0x0, offset: 0x0, rtReg: 5, threadId: 0}, } - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - effAddr := arch.AddressMask & c.addr - - // Setup - rtReg := c.rtReg - baseReg := 6 - insn := uint32((0b11_1100 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil) - mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40)) - step := state.GetStep() - - // Define LL-related params - var llAddress, llOwnerThread Word - if v.matchAddr { - llAddress = c.addr - } else { - llAddress = c.addr + 1 - } - if v.matchThreadId { - llOwnerThread = c.threadId - } else { - llOwnerThread = c.threadId + 1 - } - - // Setup state - state.GetCurrentThread().ThreadId = c.threadId - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) - state.GetRegistersRef()[baseReg] = c.base - state.GetRegistersRef()[rtReg] = value - state.LLReservationStatus = v.llReservationStatus - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread - - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - var retVal Word - if v.shouldSucceed { - retVal = 1 - expected.ExpectMemoryWordWrite(effAddr, value) - expected.LLReservationStatus = multithreaded.LLStatusNone - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } else { - retVal = 0 - } - if rtReg != 0 { - expected.ActiveThread().Registers[rtReg] = retVal - } - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + versions := GetMultiThreadedTestCases(t) + for _, ver := range versions { + for i, c := range cases { + for _, llVar := range llVariations { + tName := fmt.Sprintf("%v (%v,%v)", c.name, ver.Name, llVar.name) + t.Run(tName, func(t *testing.T) { + effAddr := arch.AddressMask & c.addr + + // Setup + rtReg := c.rtReg + baseReg := 6 + insn := uint32((0b11_1100 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40)) + step := state.GetStep() + + // Define LL-related params + var llAddress, llOwnerThread Word + if llVar.matchAddr { + llAddress = c.addr + } else { + llAddress = c.addr + 1 + } + if llVar.matchThreadId { + llOwnerThread = c.threadId + } else { + llOwnerThread = c.threadId + 1 + } + + // Setup state + state.GetCurrentThread().ThreadId = c.threadId + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[baseReg] = c.base + state.GetRegistersRef()[rtReg] = value + state.LLReservationStatus = llVar.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + var retVal Word + if llVar.shouldSucceed { + retVal = 1 + expected.ExpectMemoryWordWrite(effAddr, value) + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } else { + retVal = 0 + } + if rtReg != 0 { + expected.ActiveThread().Registers[rtReg] = retVal + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } } @@ -460,33 +478,42 @@ var NoopSyscalls64 = map[string]uint32{ "SysTimerCreate": 5216, "SysTimerSetTime": 5217, "SysTimerDelete": 5220, + "SysEventFd2": 5284, +} + +func getNoopSyscalls64(vmVersion versions.StateVersion) map[string]uint32 { + noOpCalls := maps.Clone(NoopSyscalls64) + features := versions.FeaturesForVersion(vmVersion) + if !features.SupportNoopSysEventFd2 { + delete(noOpCalls, "SysEventFd2") + } + return noOpCalls } func TestEVM_NoopSyscall64(t *testing.T) { t.Parallel() - for _, version := range GetMultiThreadedTestCases(t) { - noOpCalls := maps.Clone(NoopSyscalls64) - testNoopSyscall(t, version, noOpCalls) + for _, vmVersion := range GetMultiThreadedTestCases(t) { + noOpCalls := getNoopSyscalls64(vmVersion.Version) + testNoopSyscall(t, vmVersion, noOpCalls) } } func TestEVM_UnsupportedSyscall64(t *testing.T) { t.Parallel() - - var noopSyscallNums = maps.Values(NoopSyscalls64) - var SupportedSyscalls = []uint32{arch.SysMmap, arch.SysBrk, arch.SysClone, arch.SysExitGroup, arch.SysRead, arch.SysWrite, arch.SysFcntl, arch.SysExit, arch.SysSchedYield, arch.SysGetTID, arch.SysFutex, arch.SysOpen, arch.SysNanosleep, arch.SysClockGetTime, arch.SysGetpid} - unsupportedSyscalls := make([]uint32, 0, 400) - for i := 5000; i < 5400; i++ { - candidate := uint32(i) - if slices.Contains(SupportedSyscalls, candidate) || slices.Contains(noopSyscallNums, candidate) { - continue + for _, vmVersion := range GetMultiThreadedTestCases(t) { + var noopSyscallNums = maps.Values(getNoopSyscalls64(vmVersion.Version)) + var SupportedSyscalls = []uint32{arch.SysMmap, arch.SysBrk, arch.SysClone, arch.SysExitGroup, arch.SysRead, arch.SysWrite, arch.SysFcntl, arch.SysExit, arch.SysSchedYield, arch.SysGetTID, arch.SysFutex, arch.SysOpen, arch.SysNanosleep, arch.SysClockGetTime, arch.SysGetpid} + unsupportedSyscalls := make([]uint32, 0, 400) + for i := 5000; i < 5400; i++ { + candidate := uint32(i) + if slices.Contains(SupportedSyscalls, candidate) || slices.Contains(noopSyscallNums, candidate) { + continue + } + unsupportedSyscalls = append(unsupportedSyscalls, candidate) } - unsupportedSyscalls = append(unsupportedSyscalls, candidate) - } - for _, version := range GetMultiThreadedTestCases(t) { unsupported := unsupportedSyscalls - testUnsupportedSyscall(t, version, unsupported) + testUnsupportedSyscall(t, vmVersion, unsupported) } } diff --git a/cannon/mipsevm/tests/evm_multithreaded_test.go b/cannon/mipsevm/tests/evm_multithreaded_test.go index 3ba429108f85a..c87951f86c5f5 100644 --- a/cannon/mipsevm/tests/evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/evm_multithreaded_test.go @@ -48,47 +48,51 @@ func TestEVM_MT_LL(t *testing.T) { {name: "Unaligned addr, addr sign extended w overflow", base: 0xFF12_0001, offset: 0x8405, expectedAddr: 0xFF11_8406, memValue: posValue, retVal: posValueRet, rtReg: 5}, {name: "Return register set to 0", base: 0xFF12_0001, offset: 0x7404, expectedAddr: 0xFF12_7405, memValue: posValue, retVal: 0, rtReg: 0}, } - for i, c := range cases { - for _, withExistingReservation := range []bool{true, false} { - tName := fmt.Sprintf("%v (withExistingReservation = %v)", c.name, withExistingReservation) - t.Run(tName, func(t *testing.T) { - rtReg := c.rtReg - baseReg := 6 - insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x40)) - step := state.GetStep() - - // Set up state - testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), c.memValue) - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) - state.GetRegistersRef()[baseReg] = Word(c.base) - if withExistingReservation { - state.LLReservationStatus = multithreaded.LLStatusActive32bit - state.LLAddress = Word(c.expectedAddr + 1) - state.LLOwnerThread = 123 - } else { - state.LLReservationStatus = multithreaded.LLStatusNone - state.LLAddress = 0 - state.LLOwnerThread = 0 - } - - // Set up expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.LLReservationStatus = multithreaded.LLStatusActive32bit - expected.LLAddress = Word(c.expectedAddr) - expected.LLOwnerThread = state.GetCurrentThread().ThreadId - if rtReg != 0 { - expected.ActiveThread().Registers[rtReg] = Word(c.retVal) - } - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + for _, withExistingReservation := range []bool{true, false} { + tName := fmt.Sprintf("%v (vm = %v, withExistingReservation = %v)", c.name, ver.Name, withExistingReservation) + t.Run(tName, func(t *testing.T) { + rtReg := c.rtReg + baseReg := 6 + insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0x40)) + state := mttestutil.GetMtState(t, goVm) + step := state.GetStep() + + // Set up state + testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), c.memValue) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[baseReg] = Word(c.base) + if withExistingReservation { + state.LLReservationStatus = multithreaded.LLStatusActive32bit + state.LLAddress = Word(c.expectedAddr + 1) + state.LLOwnerThread = 123 + } else { + state.LLReservationStatus = multithreaded.LLStatusNone + state.LLAddress = 0 + state.LLOwnerThread = 0 + } + + // Set up expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.LLReservationStatus = multithreaded.LLStatusActive32bit + expected.LLAddress = Word(c.expectedAddr) + expected.LLOwnerThread = state.GetCurrentThread().ThreadId + if rtReg != 0 { + expected.ActiveThread().Registers[rtReg] = Word(c.retVal) + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } } @@ -128,64 +132,68 @@ func TestEVM_MT_SC(t *testing.T) { {name: "Unaligned addr, sign extended w overflow", base: 0xFF12_0001, offset: 0x8404, expectedAddr: 0xFF_11_8405, storeValue: 0xAABB_CCDD, rtReg: 5, threadId: 4}, {name: "Return register set to 0", base: 0xFF12_0001, offset: 0x7403, expectedAddr: 0xFF12_7404, storeValue: 0xAABB_CCDD, rtReg: 0, threadId: 4}, } - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - rtReg := c.rtReg - baseReg := 6 - insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil) - mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40)) - step := state.GetStep() - - // Define LL-related params - var llAddress, llOwnerThread Word - if v.matchAddr { - llAddress = Word(c.expectedAddr) - } else { - llAddress = Word(c.expectedAddr) + 1 - } - if v.matchThreadId { - llOwnerThread = c.threadId - } else { - llOwnerThread = c.threadId + 1 - } - - // Setup state - testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), memValue) - state.GetCurrentThread().ThreadId = c.threadId - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) - state.GetRegistersRef()[baseReg] = c.base - state.GetRegistersRef()[rtReg] = Word(c.storeValue) - state.LLReservationStatus = v.llReservationStatus - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread - - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - var retVal Word - if v.shouldSucceed { - retVal = 1 - expected.ExpectMemoryWriteUint32(t, Word(c.expectedAddr), c.storeValue) - expected.LLReservationStatus = multithreaded.LLStatusNone - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } else { - retVal = 0 - } - if rtReg != 0 { - expected.ActiveThread().Registers[rtReg] = retVal - } - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + for _, llVar := range llVariations { + tName := fmt.Sprintf("%v (%v,%v)", c.name, ver.Name, llVar.name) + t.Run(tName, func(t *testing.T) { + rtReg := c.rtReg + baseReg := 6 + insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40)) + step := state.GetStep() + + // Define LL-related params + var llAddress, llOwnerThread Word + if llVar.matchAddr { + llAddress = Word(c.expectedAddr) + } else { + llAddress = Word(c.expectedAddr) + 1 + } + if llVar.matchThreadId { + llOwnerThread = c.threadId + } else { + llOwnerThread = c.threadId + 1 + } + + // Setup state + testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), memValue) + state.GetCurrentThread().ThreadId = c.threadId + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[baseReg] = c.base + state.GetRegistersRef()[rtReg] = Word(c.storeValue) + state.LLReservationStatus = llVar.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + var retVal Word + if llVar.shouldSucceed { + retVal = 1 + expected.ExpectMemoryWriteUint32(t, Word(c.expectedAddr), c.storeValue) + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } else { + retVal = 0 + } + if rtReg != 0 { + expected.ActiveThread().Registers[rtReg] = retVal + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } } @@ -297,56 +305,61 @@ func TestEVM_SysClone_Successful(t *testing.T) { {"traverse right", true}, } - for i, c := range cases { - t.Run(c.name, func(t *testing.T) { - stackPtr := Word(100) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + testName := fmt.Sprintf("%v (%v)", c.name, ver.Name) + t.Run(testName, func(t *testing.T) { + stackPtr := Word(100) - goVm, state, contracts := setup(t, i, nil) - mttestutil.InitializeSingleThread(i*333, state, c.traverseRight) - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysClone // the syscall number - state.GetRegistersRef()[4] = exec.ValidCloneFlags // a0 - first argument, clone flags - state.GetRegistersRef()[5] = stackPtr // a1 - the stack pointer - step := state.GetStep() + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.InitializeSingleThread(i*333, state, c.traverseRight) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysClone // the syscall number + state.GetRegistersRef()[4] = exec.ValidCloneFlags // a0 - first argument, clone flags + state.GetRegistersRef()[5] = stackPtr // a1 - the stack pointer + step := state.GetStep() - // Sanity-check assumptions - require.Equal(t, Word(1), state.NextThreadId) + // Sanity-check assumptions + require.Equal(t, Word(1), state.NextThreadId) - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.Step += 1 - expectedNewThread := expected.ExpectNewThread() - expected.ActiveThreadId = expectedNewThread.ThreadId - expected.StepsSinceLastContextSwitch = 0 - if c.traverseRight { - expected.RightStackSize += 1 - } else { - expected.LeftStackSize += 1 - } - // Original thread expectations - expected.PrestateActiveThread().PC = state.GetCpu().NextPC - expected.PrestateActiveThread().NextPC = state.GetCpu().NextPC + 4 - expected.PrestateActiveThread().Registers[2] = 1 - expected.PrestateActiveThread().Registers[7] = 0 - // New thread expectations - expectedNewThread.PC = state.GetCpu().NextPC - expectedNewThread.NextPC = state.GetCpu().NextPC + 4 - expectedNewThread.ThreadId = 1 - expectedNewThread.Registers[register.RegSyscallRet1] = 0 - expectedNewThread.Registers[register.RegSyscallErrno] = 0 - expectedNewThread.Registers[register.RegSP] = stackPtr + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.Step += 1 + expectedNewThread := expected.ExpectNewThread() + expected.ActiveThreadId = expectedNewThread.ThreadId + expected.StepsSinceLastContextSwitch = 0 + if c.traverseRight { + expected.RightStackSize += 1 + } else { + expected.LeftStackSize += 1 + } + // Original thread expectations + expected.PrestateActiveThread().PC = state.GetCpu().NextPC + expected.PrestateActiveThread().NextPC = state.GetCpu().NextPC + 4 + expected.PrestateActiveThread().Registers[2] = 1 + expected.PrestateActiveThread().Registers[7] = 0 + // New thread expectations + expectedNewThread.PC = state.GetCpu().NextPC + expectedNewThread.NextPC = state.GetCpu().NextPC + 4 + expectedNewThread.ThreadId = 1 + expectedNewThread.Registers[register.RegSyscallRet1] = 0 + expectedNewThread.Registers[register.RegSyscallErrno] = 0 + expectedNewThread.Registers[register.RegSP] = stackPtr - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) - expected.Validate(t, state) - activeStack, inactiveStack := mttestutil.GetThreadStacks(state) - require.Equal(t, 2, len(activeStack)) - require.Equal(t, 0, len(inactiveStack)) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + expected.Validate(t, state) + activeStack, inactiveStack := mttestutil.GetThreadStacks(state) + require.Equal(t, 2, len(activeStack)) + require.Equal(t, 0, len(inactiveStack)) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } @@ -359,32 +372,37 @@ func TestEVM_SysGetTID(t *testing.T) { {"non-zero", 11}, } - for i, c := range cases { - t.Run(c.name, func(t *testing.T) { - goVm, state, contracts := setup(t, i*789, nil) - mttestutil.InitializeSingleThread(i*789, state, false) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + testName := fmt.Sprintf("%v (%v)", c.name, ver.Name) + t.Run(testName, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i*789))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.InitializeSingleThread(i*789, state, false) - state.GetCurrentThread().ThreadId = c.threadId - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysGetTID // Set syscall number - step := state.Step + state.GetCurrentThread().ThreadId = c.threadId + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysGetTID // Set syscall number + step := state.Step - // Set up post-state expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = c.threadId - expected.ActiveThread().Registers[7] = 0 + // Set up post-state expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = c.threadId + expected.ActiveThread().Registers[7] = 0 - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } @@ -400,39 +418,44 @@ func TestEVM_SysExit(t *testing.T) { {name: "three threads ", threadCount: 3}, } - for i, c := range cases { - t.Run(c.name, func(t *testing.T) { - exitCode := uint8(3) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + testName := fmt.Sprintf("%v (%v)", c.name, ver.Name) + t.Run(testName, func(t *testing.T) { + exitCode := uint8(3) - goVm, state, contracts := setup(t, i*133, nil) - mttestutil.SetupThreads(int64(i*1111), state, i%2 == 0, c.threadCount, 0) + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i*133))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.SetupThreads(int64(i*1111), state, i%2 == 0, c.threadCount, 0) - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysExit // Set syscall number - state.GetRegistersRef()[4] = Word(exitCode) // The first argument (exit code) - step := state.Step + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysExit // Set syscall number + state.GetRegistersRef()[4] = Word(exitCode) // The first argument (exit code) + step := state.Step - // Set up expectations - expected := mttestutil.NewExpectedMTState(state) - expected.Step += 1 - expected.StepsSinceLastContextSwitch += 1 - expected.ActiveThread().Exited = true - expected.ActiveThread().ExitCode = exitCode - if c.shouldExitGlobally { - expected.Exited = true - expected.ExitCode = exitCode - } + // Set up expectations + expected := mttestutil.NewExpectedMTState(state) + expected.Step += 1 + expected.StepsSinceLastContextSwitch += 1 + expected.ActiveThread().Exited = true + expected.ActiveThread().ExitCode = exitCode + if c.shouldExitGlobally { + expected.Exited = true + expected.ExitCode = exitCode + } - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } @@ -449,41 +472,46 @@ func TestEVM_PopExitedThread(t *testing.T) { {name: "traverse left, switch directions", traverseRight: false, activeStackThreadCount: 1, expectTraverseRightPostState: true}, } - for i, c := range cases { - t.Run(c.name, func(t *testing.T) { - goVm, state, contracts := setup(t, i*133, nil) - mttestutil.SetupThreads(int64(i*222), state, c.traverseRight, c.activeStackThreadCount, 1) - step := state.Step + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + testName := fmt.Sprintf("%v (%v)", c.name, ver.Name) + t.Run(testName, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i*133))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.SetupThreads(int64(i*222), state, c.traverseRight, c.activeStackThreadCount, 1) + step := state.Step - // Setup thread to be dropped - threadToPop := state.GetCurrentThread() - threadToPop.Exited = true - threadToPop.ExitCode = 1 + // Setup thread to be dropped + threadToPop := state.GetCurrentThread() + threadToPop.Exited = true + threadToPop.ExitCode = 1 - // Set up expectations - expected := mttestutil.NewExpectedMTState(state) - expected.Step += 1 - expected.ActiveThreadId = mttestutil.FindNextThreadExcluding(state, threadToPop.ThreadId).ThreadId - expected.StepsSinceLastContextSwitch = 0 - expected.ThreadCount -= 1 - expected.TraverseRight = c.expectTraverseRightPostState - expected.Thread(threadToPop.ThreadId).Dropped = true - if c.traverseRight { - expected.RightStackSize -= 1 - } else { - expected.LeftStackSize -= 1 - } + // Set up expectations + expected := mttestutil.NewExpectedMTState(state) + expected.Step += 1 + expected.ActiveThreadId = mttestutil.FindNextThreadExcluding(state, threadToPop.ThreadId).ThreadId + expected.StepsSinceLastContextSwitch = 0 + expected.ThreadCount -= 1 + expected.TraverseRight = c.expectTraverseRightPostState + expected.Thread(threadToPop.ThreadId).Dropped = true + if c.traverseRight { + expected.RightStackSize -= 1 + } else { + expected.LeftStackSize -= 1 + } - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } @@ -510,45 +538,50 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { {name: "memory mismatch w timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_00, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_F8, actualValue: 0xF8, timeout: 2000000, shouldFail: true}, {name: "memory mismatch w timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_0F, effAddr: 0xFF_FF_FF_FF_FF_FF_12_0C, targetValue: 0xFF_FF_FF_01, actualValue: 0xFF_FF_FF_02, timeout: 2000000, shouldFail: true}, } - for i, c := range cases { - t.Run(c.name, func(t *testing.T) { - rand := testutil.NewRandHelper(int64(i * 33)) - goVm, state, contracts := setup(t, i*1234, nil, testutil.WithPCAndNextPC(0x04)) - step := state.GetStep() + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + testName := fmt.Sprintf("%v (%v)", c.name, ver.Name) + t.Run(testName, func(t *testing.T) { + rand := testutil.NewRandHelper(int64(i * 33)) + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i*1234)), testutil.WithPCAndNextPC(0x04)) + state := mttestutil.GetMtState(t, goVm) + step := state.GetStep() - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - testutil.RandomizeWordAndSetUint32(state.GetMemory(), Word(c.effAddr), c.actualValue, int64(i+22)) - state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number - state.GetRegistersRef()[4] = Word(c.addressParam) - state.GetRegistersRef()[5] = exec.FutexWaitPrivate - // Randomize upper bytes of futex target - state.GetRegistersRef()[6] = (rand.Word() & ^Word(0xFF_FF_FF_FF)) | Word(c.targetValue) - state.GetRegistersRef()[7] = Word(c.timeout) - - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.Step += 1 - expected.ActiveThread().PC = state.GetCpu().NextPC - expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4 - if c.shouldFail { - expected.StepsSinceLastContextSwitch += 1 - expected.ActiveThread().Registers[2] = exec.SysErrorSignal - expected.ActiveThread().Registers[7] = exec.MipsEAGAIN - } else { - // Return empty result and preempt thread - expected.ActiveThread().Registers[2] = 0 - expected.ActiveThread().Registers[7] = 0 - expected.ExpectPreemption(state) - } + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + testutil.RandomizeWordAndSetUint32(state.GetMemory(), Word(c.effAddr), c.actualValue, int64(i+22)) + state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number + state.GetRegistersRef()[4] = Word(c.addressParam) + state.GetRegistersRef()[5] = exec.FutexWaitPrivate + // Randomize upper bytes of futex target + state.GetRegistersRef()[6] = (rand.Word() & ^Word(0xFF_FF_FF_FF)) | Word(c.targetValue) + state.GetRegistersRef()[7] = Word(c.timeout) - // State transition - stepWitness, err := goVm.Step(true) - require.NoError(t, err) + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.Step += 1 + expected.ActiveThread().PC = state.GetCpu().NextPC + expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4 + if c.shouldFail { + expected.StepsSinceLastContextSwitch += 1 + expected.ActiveThread().Registers[2] = exec.SysErrorSignal + expected.ActiveThread().Registers[7] = exec.MipsEAGAIN + } else { + // Return empty result and preempt thread + expected.ActiveThread().Registers[2] = 0 + expected.ActiveThread().Registers[7] = 0 + expected.ExpectPreemption(state) + } - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // State transition + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } @@ -578,34 +611,38 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { {name: "Traverse left, single thread", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false}, {name: "Traverse left, single thread, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false}, } - for i, c := range cases { - t.Run(c.name, func(t *testing.T) { - goVm, state, contracts := setup(t, i*1122, nil) - mttestutil.SetupThreads(int64(i*2244), state, c.traverseRight, c.activeThreadCount, c.inactiveThreadCount) - step := state.Step + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + testName := fmt.Sprintf("%v (%v)", c.name, ver.Name) + t.Run(testName, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i*1122))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.SetupThreads(int64(i*2244), state, c.traverseRight, c.activeThreadCount, c.inactiveThreadCount) + step := state.Step - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number - state.GetRegistersRef()[4] = Word(c.addressParam) - state.GetRegistersRef()[5] = exec.FutexWakePrivate + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number + state.GetRegistersRef()[4] = Word(c.addressParam) + state.GetRegistersRef()[5] = exec.FutexWakePrivate - // Set up post-state expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = 0 - expected.ActiveThread().Registers[7] = 0 - expected.ExpectPreemption(state) + // Set up post-state expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = 0 + expected.ActiveThread().Registers[7] = 0 + expected.ExpectPreemption(state) - // State transition - stepWitness, err := goVm.Step(true) - require.NoError(t, err) + // State transition + stepWitness, err := goVm.Step(true) + require.NoError(t, err) - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } - } func TestEVM_SysFutex_UnsupportedOp(t *testing.T) { @@ -654,34 +691,39 @@ func TestEVM_SysFutex_UnsupportedOp(t *testing.T) { "FUTEX_CMP_REQUEUE_PI_PRIVATE": (FUTEX_CMP_REQUEUE_PI | FUTEX_PRIVATE_FLAG), } - for name, op := range unsupportedFutexOps { - t.Run(name, func(t *testing.T) { - goVm, state, contracts := setup(t, int(op), nil) - step := state.GetStep() + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for name, op := range unsupportedFutexOps { + testName := fmt.Sprintf("%v (%v)", name, ver.Name) + t.Run(testName, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(op))) + state := mttestutil.GetMtState(t, goVm) + step := state.GetStep() - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number - state.GetRegistersRef()[5] = op + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number + state.GetRegistersRef()[5] = op - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.Step += 1 - expected.StepsSinceLastContextSwitch += 1 - expected.ActiveThread().PC = state.GetCpu().NextPC - expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4 - expected.ActiveThread().Registers[2] = exec.SysErrorSignal - expected.ActiveThread().Registers[7] = exec.MipsEINVAL + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.Step += 1 + expected.StepsSinceLastContextSwitch += 1 + expected.ActiveThread().PC = state.GetCpu().NextPC + expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4 + expected.ActiveThread().Registers[2] = exec.SysErrorSignal + expected.ActiveThread().Registers[7] = exec.MipsEINVAL - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } @@ -706,84 +748,101 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) { {name: "Do not change directions", activeThreads: 3, inactiveThreads: 0}, } - for i, c := range cases { - for _, traverseRight := range []bool{true, false} { - testName := fmt.Sprintf("%v: %v (traverseRight = %v)", syscallName, c.name, traverseRight) - t.Run(testName, func(t *testing.T) { - goVm, state, contracts := setup(t, i*789, nil) - mttestutil.SetupThreads(int64(i*3259), state, traverseRight, c.activeThreads, c.inactiveThreads) + versions := GetMultiThreadedTestCases(t) + for _, ver := range versions { + for i, c := range cases { + for _, traverseRight := range []bool{true, false} { + testName := fmt.Sprintf("%v: %v (vm = %v, traverseRight = %v)", syscallName, c.name, ver.Name, traverseRight) + t.Run(testName, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i*789))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.SetupThreads(int64(i*3259), state, traverseRight, c.activeThreads, c.inactiveThreads) - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = Word(syscallNum) // Set syscall number - step := state.Step + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = Word(syscallNum) // Set syscall number + step := state.Step - // Set up post-state expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ExpectPreemption(state) - expected.PrestateActiveThread().Registers[2] = 0 - expected.PrestateActiveThread().Registers[7] = 0 + // Set up post-state expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ExpectPreemption(state) + expected.PrestateActiveThread().Registers[2] = 0 + expected.PrestateActiveThread().Registers[7] = 0 - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } } func TestEVM_SysOpen(t *testing.T) { - goVm, state, contracts := setup(t, 5512, nil) - - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysOpen // Set syscall number - step := state.Step - - // Set up post-state expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = exec.SysErrorSignal - expected.ActiveThread().Registers[7] = exec.MipsEBADF - - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) - - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + t.Run(ver.Name, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(5512))) + state := mttestutil.GetMtState(t, goVm) + + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysOpen // Set syscall number + step := state.Step + + // Set up post-state expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = exec.SysErrorSignal + expected.ActiveThread().Registers[7] = exec.MipsEBADF + + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) + + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } + } func TestEVM_SysGetPID(t *testing.T) { - goVm, state, contracts := setup(t, 1929, nil) - - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysGetpid // Set syscall number - step := state.Step - - // Set up post-state expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = 0 - expected.ActiveThread().Registers[7] = 0 - - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) - - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + t.Run(ver.Name, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(1929))) + state := mttestutil.GetMtState(t, goVm) + + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysGetpid // Set syscall number + step := state.Step + + // Set up post-state expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = 0 + expected.ActiveThread().Registers[7] = 0 + + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) + + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } func TestEVM_SysClockGettimeMonotonic(t *testing.T) { @@ -823,93 +882,103 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) { {"aligned timespec address", 0x1000}, {"unaligned timespec address", 0x1003}, } - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - goVm, state, contracts := setup(t, 2101, nil) - mttestutil.InitializeSingleThread(2101+i, state, i%2 == 1) - effAddr := c.timespecAddr & arch.AddressMask - effAddr2 := effAddr + arch.WordSizeBytes - step := state.Step + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + for _, llVar := range llVariations { + tName := fmt.Sprintf("%v (%v,%v)", c.name, ver.Name, llVar.name) + t.Run(tName, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(2101))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.InitializeSingleThread(2101+i, state, i%2 == 1) + effAddr := c.timespecAddr & arch.AddressMask + effAddr2 := effAddr + arch.WordSizeBytes + step := state.Step + + // Define LL-related params + var llAddress, llOwnerThread Word + if llVar.matchEffAddr { + llAddress = effAddr + } else if llVar.matchEffAddr2 { + llAddress = effAddr2 + } else { + llAddress = effAddr2 + 8 + } + if llVar.matchThreadId { + llOwnerThread = state.GetCurrentThread().ThreadId + } else { + llOwnerThread = state.GetCurrentThread().ThreadId + 1 + } - // Define LL-related params - var llAddress, llOwnerThread Word - if v.matchEffAddr { - llAddress = effAddr - } else if v.matchEffAddr2 { - llAddress = effAddr2 - } else { - llAddress = effAddr2 + 8 - } - if v.matchThreadId { - llOwnerThread = state.GetCurrentThread().ThreadId - } else { - llOwnerThread = state.GetCurrentThread().ThreadId + 1 - } - - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysClockGetTime // Set syscall number - state.GetRegistersRef()[4] = clkid // a0 - state.GetRegistersRef()[5] = c.timespecAddr // a1 - state.LLReservationStatus = v.llReservationStatus - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysClockGetTime // Set syscall number + state.GetRegistersRef()[4] = clkid // a0 + state.GetRegistersRef()[5] = c.timespecAddr // a1 + state.LLReservationStatus = llVar.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = 0 - expected.ActiveThread().Registers[7] = 0 - next := state.Step + 1 - var secs, nsecs Word - if clkid == exec.ClockGettimeMonotonicFlag { - secs = Word(next / exec.HZ) - nsecs = Word((next % exec.HZ) * (1_000_000_000 / exec.HZ)) - } - expected.ExpectMemoryWordWrite(effAddr, secs) - expected.ExpectMemoryWordWrite(effAddr2, nsecs) - if v.shouldClearReservation { - expected.LLReservationStatus = multithreaded.LLStatusNone - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = 0 + expected.ActiveThread().Registers[7] = 0 + next := state.Step + 1 + var secs, nsecs Word + if clkid == exec.ClockGettimeMonotonicFlag { + secs = Word(next / exec.HZ) + nsecs = Word((next % exec.HZ) * (1_000_000_000 / exec.HZ)) + } + expected.ExpectMemoryWordWrite(effAddr, secs) + expected.ExpectMemoryWordWrite(effAddr2, nsecs) + if llVar.shouldClearReservation { + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } } func TestEVM_SysClockGettimeNonMonotonic(t *testing.T) { - goVm, state, contracts := setup(t, 2101, nil) - - timespecAddr := Word(0x1000) - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysClockGetTime // Set syscall number - state.GetRegistersRef()[4] = 0xDEAD // a0 - invalid clockid - state.GetRegistersRef()[5] = timespecAddr // a1 - step := state.Step - - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = exec.SysErrorSignal - expected.ActiveThread().Registers[7] = exec.MipsEINVAL - - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) - - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + t.Run(ver.Name, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(2101))) + state := mttestutil.GetMtState(t, goVm) + + timespecAddr := Word(0x1000) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysClockGetTime // Set syscall number + state.GetRegistersRef()[4] = 0xDEAD // a0 - invalid clockid + state.GetRegistersRef()[5] = timespecAddr // a1 + step := state.Step + + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = exec.SysErrorSignal + expected.ActiveThread().Registers[7] = exec.MipsEINVAL + + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) + + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } var NoopSyscalls = map[string]uint32{ @@ -951,7 +1020,7 @@ var NoopSyscalls = map[string]uint32{ func TestEVM_NoopSyscall32(t *testing.T) { testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_NoopSyscall64") - testNoopSyscall(t, GetMultiThreadedTestCase(t, versions.VersionMultiThreaded), NoopSyscalls) + testNoopSyscall(t, GetMultiThreadedTestCase(t, versions.VersionMultiThreaded_v2), NoopSyscalls) } func TestEVM_UnsupportedSyscall32(t *testing.T) { @@ -989,18 +1058,22 @@ func TestEVM_EmptyThreadStacks(t *testing.T) { // Generate proof variations proofVariations := GenerateEmptyThreadProofVariations(t) - for i, c := range cases { - for _, proofCase := range proofVariations { - testName := fmt.Sprintf("%v (proofCase=%v)", c.name, proofCase.Name) - t.Run(testName, func(t *testing.T) { - goVm, state, contracts := setup(t, i*123, nil) - mttestutil.SetupThreads(int64(i*123), state, c.traverseRight, 0, c.otherStackSize) - - require.PanicsWithValue(t, "Active thread stack is empty", func() { _, _ = goVm.Step(false) }) - - errorMessage := "active thread stack is empty" - testutil.AssertEVMReverts(t, state, contracts, tracer, proofCase.Proof, testutil.CreateErrorStringMatcher(errorMessage)) - }) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + for _, proofCase := range proofVariations { + testName := fmt.Sprintf("%v (vm=%v,proofCase=%v)", c.name, ver.Name, proofCase.Name) + t.Run(testName, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i*123))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.SetupThreads(int64(i*123), state, c.traverseRight, 0, c.otherStackSize) + + require.PanicsWithValue(t, "Active thread stack is empty", func() { _, _ = goVm.Step(false) }) + + errorMessage := "active thread stack is empty" + testutil.AssertEVMReverts(t, state, ver.Contracts, tracer, proofCase.Proof, testutil.CreateErrorStringMatcher(errorMessage)) + }) + } } } } @@ -1015,40 +1088,44 @@ func TestEVM_NormalTraversal_Full(t *testing.T) { {"3 threads", 3}, } - for i, c := range cases { - for _, traverseRight := range []bool{true, false} { - testName := fmt.Sprintf("%v (traverseRight = %v)", c.name, traverseRight) - t.Run(testName, func(t *testing.T) { - // Setup - goVm, state, contracts := setup(t, i*789, nil) - mttestutil.SetupThreads(int64(i*2947), state, traverseRight, c.threadCount, 0) - step := state.Step - - // Loop through all the threads to get back to the starting state - iterations := c.threadCount * 2 - for i := 0; i < iterations; i++ { - // Set up thread to yield - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = Word(arch.SysSchedYield) - - // Set up post-state expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ActiveThread().Registers[2] = 0 - expected.ActiveThread().Registers[7] = 0 - expected.ExpectStep() - expected.ExpectPreemption(state) - - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) - - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - } - }) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + for _, traverseRight := range []bool{true, false} { + testName := fmt.Sprintf("%v (vm = %v, traverseRight = %v)", c.name, ver.Name, traverseRight) + t.Run(testName, func(t *testing.T) { + // Setup + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i*789))) + state := mttestutil.GetMtState(t, goVm) + mttestutil.SetupThreads(int64(i*2947), state, traverseRight, c.threadCount, 0) + step := state.Step + + // Loop through all the threads to get back to the starting state + iterations := c.threadCount * 2 + for i := 0; i < iterations; i++ { + // Set up thread to yield + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = Word(arch.SysSchedYield) + + // Set up post-state expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ActiveThread().Registers[2] = 0 + expected.ActiveThread().Registers[7] = 0 + expected.ExpectStep() + expected.ExpectPreemption(state) + + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) + + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + } + }) + } } } } @@ -1064,50 +1141,41 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) { {name: "beyond threshold", stepsSinceLastContextSwitch: exec.SchedQuantum + 1, shouldPreempt: true}, } - for i, c := range cases { - t.Run(c.name, func(t *testing.T) { - goVm, state, contracts := setup(t, i*789, nil) - // Setup basic getThreadId syscall instruction - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysGetTID // Set syscall number - state.StepsSinceLastContextSwitch = c.stepsSinceLastContextSwitch - step := state.Step + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + testName := fmt.Sprintf("%v (%v)", c.name, ver.Name) + t.Run(testName, func(t *testing.T) { + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i*789))) + state := mttestutil.GetMtState(t, goVm) + // Setup basic getThreadId syscall instruction + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysGetTID // Set syscall number + state.StepsSinceLastContextSwitch = c.stepsSinceLastContextSwitch + step := state.Step - // Set up post-state expectations - expected := mttestutil.NewExpectedMTState(state) - if c.shouldPreempt { - expected.Step += 1 - expected.ExpectPreemption(state) - } else { - // Otherwise just expect a normal step - expected.ExpectStep() - expected.ActiveThread().Registers[2] = state.GetCurrentThread().ThreadId - expected.ActiveThread().Registers[7] = 0 - } + // Set up post-state expectations + expected := mttestutil.NewExpectedMTState(state) + if c.shouldPreempt { + expected.Step += 1 + expected.ExpectPreemption(state) + } else { + // Otherwise just expect a normal step + expected.ExpectStep() + expected.ActiveThread().Registers[2] = state.GetCurrentThread().ThreadId + expected.ActiveThread().Registers[7] = 0 + } - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } - -func setup(t require.TestingT, randomSeed int, preimageOracle mipsevm.PreimageOracle, opts ...testutil.StateOption) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) { - cases := GetMultiThreadedTestCases(t) - require.NotZero(t, len(cases), "must have at least one supported multithreaded test case") - // Use the most recent supported version - return setupWithTestCase(t, cases[len(cases)-1], randomSeed, preimageOracle, opts...) -} - -func setupWithTestCase(t require.TestingT, v VersionedVMTestCase, randomSeed int, preimageOracle mipsevm.PreimageOracle, opts ...testutil.StateOption) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) { - allOpts := append([]testutil.StateOption{testutil.WithRandomization(int64(randomSeed))}, opts...) - vm := v.VMFactory(preimageOracle, os.Stdout, os.Stderr, testutil.CreateLogger(), allOpts...) - state := mttestutil.GetMtState(t, vm) - return vm, state, v.Contracts -} diff --git a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go index 7b3d9d2756bb7..9ae2ce5e5eade 100644 --- a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go @@ -17,8 +17,8 @@ import ( func FuzzStateSyscallCloneMT(f *testing.F) { versions := GetMultiThreadedTestCases(f) require.NotZero(f, len(versions), "must have at least one multithreaded version supported") - f.Fuzz(func(t *testing.T, nextThreadId, stackPtr Word, seed int64, version int) { - v := versions[version%len(versions)] + f.Fuzz(func(t *testing.T, nextThreadId, stackPtr Word, seed int64, version uint) { + v := versions[int(version)%len(versions)] goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed)) state := mttestutil.GetMtState(t, goVm) // Update existing threads to avoid collision with nextThreadId diff --git a/cannon/mipsevm/tests/helpers.go b/cannon/mipsevm/tests/helpers.go index 180473127416a..79cade72ca097 100644 --- a/cannon/mipsevm/tests/helpers.go +++ b/cannon/mipsevm/tests/helpers.go @@ -2,6 +2,7 @@ package tests import ( "io" + "os" "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" "github.com/ethereum/go-ethereum/log" @@ -162,3 +163,10 @@ func GenerateEmptyThreadProofVariations(t require.TestingT) []threadProofTestcas {Name: "nil thread bytes proof", Proof: nilBytesThreadProof}, } } + +func setupWithTestCase(t require.TestingT, v VersionedVMTestCase, randomSeed int, preimageOracle mipsevm.PreimageOracle, opts ...testutil.StateOption) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) { + allOpts := append([]testutil.StateOption{testutil.WithRandomization(int64(randomSeed))}, opts...) + vm := v.VMFactory(preimageOracle, os.Stdout, os.Stderr, testutil.CreateLogger(), allOpts...) + state := mttestutil.GetMtState(t, vm) + return vm, state, v.Contracts +} diff --git a/cannon/mipsevm/tests/testfuncs_test.go b/cannon/mipsevm/tests/testfuncs_test.go index c7d86240b4a4d..7939b4bef10c1 100644 --- a/cannon/mipsevm/tests/testfuncs_test.go +++ b/cannon/mipsevm/tests/testfuncs_test.go @@ -292,48 +292,52 @@ func testMTStoreOpsClearMemReservation(t *testing.T, cases []testMTStoreOpsClear //rt := Word(0x12_34_56_78_12_34_56_78) baseReg := uint32(5) rtReg := uint32(6) - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - t.Parallel() - insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x08)) - step := state.GetStep() - - // Define LL-related params - llAddress := c.effAddr + v.effAddrOffset - llOwnerThread := state.GetCurrentThread().ThreadId - if !v.matchThreadId { - llOwnerThread += 1 - } + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + for _, llVariation := range llVariations { + tName := fmt.Sprintf("%v (%v,%v)", c.name, ver.Name, llVariation.name) + t.Run(tName, func(t *testing.T) { + t.Parallel() + insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm := ver.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0x08)) + state := mttestutil.GetMtState(t, goVm) + step := state.GetStep() + + // Define LL-related params + llAddress := c.effAddr + llVariation.effAddrOffset + llOwnerThread := state.GetCurrentThread().ThreadId + if !llVariation.matchThreadId { + llOwnerThread += 1 + } + + // Setup state + state.GetRegistersRef()[rtReg] = rt + state.GetRegistersRef()[baseReg] = c.base + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetMemory().SetWord(c.effAddr, c.preMem) + state.LLReservationStatus = llVariation.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ExpectMemoryWordWrite(c.effAddr, c.postMem) + if llVariation.shouldClearReservation { + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } - // Setup state - state.GetRegistersRef()[rtReg] = rt - state.GetRegistersRef()[baseReg] = c.base - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) - state.GetMemory().SetWord(c.effAddr, c.preMem) - state.LLReservationStatus = v.llReservationStatus - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread - - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ExpectMemoryWordWrite(c.effAddr, c.postMem) - if v.shouldClearReservation { - expected.LLReservationStatus = multithreaded.LLStatusNone - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) + stepWitness, err := goVm.Step(true) + require.NoError(t, err) - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + }) + } } } } @@ -366,62 +370,66 @@ func testMTSysReadPreimage(t *testing.T, preimageValue []byte, cases []testMTSys {name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, } - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - t.Parallel() - effAddr := arch.AddressMask & c.addr - preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey() - oracle := testutil.StaticOracle(t, preimageValue) - goVm, state, contracts := setup(t, i, oracle) - step := state.GetStep() - - // Define LL-related params - llAddress := effAddr + v.effAddrOffset - llOwnerThread := state.GetCurrentThread().ThreadId - if !v.matchThreadId { - llOwnerThread += 1 - } - - // Set up state - state.PreimageKey = preimageKey - state.PreimageOffset = c.preimageOffset - state.GetRegistersRef()[2] = arch.SysRead - state.GetRegistersRef()[4] = exec.FdPreimageRead - state.GetRegistersRef()[5] = c.addr - state.GetRegistersRef()[6] = c.count - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) - state.LLReservationStatus = v.llReservationStatus - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread - state.GetMemory().SetWord(effAddr, c.prestateMem) - - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = c.writeLen - expected.ActiveThread().Registers[7] = 0 // no error - expected.PreimageOffset += c.writeLen - expected.ExpectMemoryWordWrite(effAddr, c.postateMem) - if v.shouldClearReservation { - expected.LLReservationStatus = multithreaded.LLStatusNone - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } - - if c.shouldPanic { - require.Panics(t, func() { _, _ = goVm.Step(true) }) - testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, contracts) - } else { - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - } - }) + vmVersions := GetMultiThreadedTestCases(t) + for _, ver := range vmVersions { + for i, c := range cases { + for _, llVariation := range llVariations { + tName := fmt.Sprintf("%v (%v,%v)", c.name, ver.Name, llVariation.name) + t.Run(tName, func(t *testing.T) { + t.Parallel() + effAddr := arch.AddressMask & c.addr + preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey() + oracle := testutil.StaticOracle(t, preimageValue) + goVm := ver.VMFactory(oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i))) + state := mttestutil.GetMtState(t, goVm) + step := state.GetStep() + + // Define LL-related params + llAddress := effAddr + llVariation.effAddrOffset + llOwnerThread := state.GetCurrentThread().ThreadId + if !llVariation.matchThreadId { + llOwnerThread += 1 + } + + // Set up state + state.PreimageKey = preimageKey + state.PreimageOffset = c.preimageOffset + state.GetRegistersRef()[2] = arch.SysRead + state.GetRegistersRef()[4] = exec.FdPreimageRead + state.GetRegistersRef()[5] = c.addr + state.GetRegistersRef()[6] = c.count + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) + state.LLReservationStatus = llVariation.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + state.GetMemory().SetWord(effAddr, c.prestateMem) + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = c.writeLen + expected.ActiveThread().Registers[7] = 0 // no error + expected.PreimageOffset += c.writeLen + expected.ExpectMemoryWordWrite(effAddr, c.postateMem) + if llVariation.shouldClearReservation { + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } + + if c.shouldPanic { + require.Panics(t, func() { _, _ = goVm.Step(true) }) + testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, ver.Contracts) + } else { + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), ver.Contracts) + } + }) + } } } } diff --git a/cannon/mipsevm/testutil/arch.go b/cannon/mipsevm/testutil/arch.go index 8336212323f74..a7d03dba2b860 100644 --- a/cannon/mipsevm/testutil/arch.go +++ b/cannon/mipsevm/testutil/arch.go @@ -64,13 +64,20 @@ func ToSignedInteger(x Word) arch.SignedInteger { return arch.SignedInteger(x) } -// Cannon32OnlyTest skips the test if it's not a cannon64 build +// Cannon32OnlyTest skips the test if it targets 64-bit Cannon func Cannon32OnlyTest(t testing.TB, msg string, args ...any) { if !arch.IsMips32 { t.Skipf(msg, args...) } } +// Cannon64OnlyTest skips the test if it targets 32-bit Cannon +func Cannon64OnlyTest(t testing.TB, msg string, args ...any) { + if arch.IsMips32 { + t.Skipf(msg, args...) + } +} + // FlipSign flips the sign of a 2's complement Word func FlipSign(val Word) Word { return ^val + 1 diff --git a/cannon/mipsevm/testutil/elf.go b/cannon/mipsevm/testutil/elf.go index 3e1896963cda4..b4b27dbc377c1 100644 --- a/cannon/mipsevm/testutil/elf.go +++ b/cannon/mipsevm/testutil/elf.go @@ -34,5 +34,5 @@ func ProgramPath(programName string) string { if !arch.IsMips32 { basename = programName + ".64.elf" } - return "../../testdata/example/bin/" + basename + return "../../testdata/go-1-23/bin/" + basename } diff --git a/cannon/mipsevm/testutil/vmtests.go b/cannon/mipsevm/testutil/vmtests.go index 333720a4ec025..e5b64e1f46170 100644 --- a/cannon/mipsevm/testutil/vmtests.go +++ b/cannon/mipsevm/testutil/vmtests.go @@ -103,7 +103,7 @@ func RunVMTest_Hello[T mipsevm.FPVMState](t *testing.T, initState program.Create var stdOutBuf, stdErrBuf bytes.Buffer us := vmFactory(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), CreateLogger(), meta) - maxSteps := 430_000 + maxSteps := 450_000 for i := 0; i < maxSteps; i++ { if us.GetState().GetExited() { break diff --git a/cannon/mipsevm/versions/state.go b/cannon/mipsevm/versions/state.go index 2ef3e1396fbd5..3358ba7beb16a 100644 --- a/cannon/mipsevm/versions/state.go +++ b/cannon/mipsevm/versions/state.go @@ -80,6 +80,9 @@ func (s *VersionedState) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, func FeaturesForVersion(version StateVersion) mipsevm.FeatureToggles { features := mipsevm.FeatureToggles{} // Set any required feature toggles based on the state version here. + if version >= VersionMultiThreaded64_v4 { + features.SupportNoopSysEventFd2 = true + } return features } diff --git a/cannon/mipsevm/versions/testdata/states/7.bin.gz b/cannon/mipsevm/versions/testdata/states/7.bin.gz new file mode 100644 index 0000000000000..aede045fb1e4c Binary files /dev/null and b/cannon/mipsevm/versions/testdata/states/7.bin.gz differ diff --git a/cannon/mipsevm/versions/version.go b/cannon/mipsevm/versions/version.go index ab459f7a24dfc..90d670f61e9e8 100644 --- a/cannon/mipsevm/versions/version.go +++ b/cannon/mipsevm/versions/version.go @@ -13,16 +13,18 @@ const ( // VersionMultiThreaded is the original implementation of 32-bit multithreaded cannon, tagged at cannon/v1.3.0 VersionMultiThreaded // VersionSingleThreaded2 is based on VersionSingleThreaded with the addition of support for fcntl(F_GETFD) syscall - // This is the latest 32-bit single-threaded vm + // This is the latest 32-bit single-threaded vm, tagged at cannon/v1.4.0 VersionSingleThreaded2 // VersionMultiThreaded64 is the original 64-bit MTCannon implementation (pre-audit), tagged at cannon/v1.2.0 VersionMultiThreaded64 // VersionMultiThreaded64_v2 includes an audit fix to ensure futex values are always 32-bit, tagged at cannon/v1.3.0 VersionMultiThreaded64_v2 - // VersionMultiThreaded_v2 is the latest 32-bit multithreaded vm + // VersionMultiThreaded_v2 is the latest 32-bit multithreaded vm, tagged at cannon/v1.4.0 VersionMultiThreaded_v2 - // VersionMultiThreaded64_v3 is the latest 64-bit multithreaded vm + // VersionMultiThreaded64_v3 includes futex handling simplification VersionMultiThreaded64_v3 + // VersionMultiThreaded64_v4 is the latest 64-bit multithreaded vm, includes support for new syscall eventfd2 + VersionMultiThreaded64_v4 ) var StateVersionTypes = []StateVersion{ @@ -33,6 +35,7 @@ var StateVersionTypes = []StateVersion{ VersionMultiThreaded64_v2, VersionMultiThreaded_v2, VersionMultiThreaded64_v3, + VersionMultiThreaded64_v4, } func (s StateVersion) String() string { @@ -51,6 +54,8 @@ func (s StateVersion) String() string { return "multithreaded-2" case VersionMultiThreaded64_v3: return "multithreaded64-3" + case VersionMultiThreaded64_v4: + return "multithreaded64-4" default: return "unknown" } @@ -72,6 +77,8 @@ func ParseStateVersion(ver string) (StateVersion, error) { return VersionMultiThreaded_v2, nil case "multithreaded64-3": return VersionMultiThreaded64_v3, nil + case "multithreaded64-4": + return VersionMultiThreaded64_v4, nil default: return StateVersion(0), errors.New("unknown state version") } @@ -91,7 +98,7 @@ func GetStateVersionStrings() []string { // IsSupportedMultiThreaded64 returns true if the state version is a 64-bit multithreaded VM that is currently supported func IsSupportedMultiThreaded64(ver StateVersion) bool { - return ver == VersionMultiThreaded64_v3 + return ver == VersionMultiThreaded64_v3 || ver == VersionMultiThreaded64_v4 } // IsSupportedMultiThreaded returns true if the state version is a 32-bit multithreaded VM that is currently supported diff --git a/cannon/testdata/Makefile b/cannon/testdata/Makefile new file mode 100644 index 0000000000000..90fd1b552dee3 --- /dev/null +++ b/cannon/testdata/Makefile @@ -0,0 +1,13 @@ +all: elf + +go1-22: + make -C ./go-1-22 elf +.PHONY: go1-22 + +go1-23: + make -C ./go-1-23 elf +.PHONY: go1-23 + +.PHONY: elf +elf: go1-22 go1-23 + diff --git a/cannon/testdata/example/entry/go.mod b/cannon/testdata/example/entry/go.mod deleted file mode 100644 index 3c724bb756493..0000000000000 --- a/cannon/testdata/example/entry/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module entry - -go 1.22.0 - -toolchain go1.22.7 diff --git a/cannon/testdata/example/mt-atomic/go.mod b/cannon/testdata/example/mt-atomic/go.mod deleted file mode 100644 index cdd5266ce48e8..0000000000000 --- a/cannon/testdata/example/mt-atomic/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module atomic - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/mt-cond/go.mod b/cannon/testdata/example/mt-cond/go.mod deleted file mode 100644 index d24ddaaf92ffd..0000000000000 --- a/cannon/testdata/example/mt-cond/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module cond - -go 1.22.0 - -toolchain go1.22.7 diff --git a/cannon/testdata/example/mt-general/go.mod b/cannon/testdata/example/mt-general/go.mod deleted file mode 100644 index 047d77f4c5d57..0000000000000 --- a/cannon/testdata/example/mt-general/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module mtgeneral - -go 1.22.0 - -toolchain go1.22.7 diff --git a/cannon/testdata/example/mt-map/go.mod b/cannon/testdata/example/mt-map/go.mod deleted file mode 100644 index 4c237f5f85805..0000000000000 --- a/cannon/testdata/example/mt-map/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module map - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/mt-mutex/go.mod b/cannon/testdata/example/mt-mutex/go.mod deleted file mode 100644 index 9f6aa60e7b81c..0000000000000 --- a/cannon/testdata/example/mt-mutex/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module mutex - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/mt-once/go.mod b/cannon/testdata/example/mt-once/go.mod deleted file mode 100644 index d9c104e093f64..0000000000000 --- a/cannon/testdata/example/mt-once/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module once - -go 1.22.0 - -toolchain go1.22.7 diff --git a/cannon/testdata/example/mt-oncefunc/go.mod b/cannon/testdata/example/mt-oncefunc/go.mod deleted file mode 100644 index 8c93d188822ef..0000000000000 --- a/cannon/testdata/example/mt-oncefunc/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module oncefunc - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/mt-pool/go.mod b/cannon/testdata/example/mt-pool/go.mod deleted file mode 100644 index d985221d0f4a5..0000000000000 --- a/cannon/testdata/example/mt-pool/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module pool - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/mt-rwmutex/go.mod b/cannon/testdata/example/mt-rwmutex/go.mod deleted file mode 100644 index 1955674e021ac..0000000000000 --- a/cannon/testdata/example/mt-rwmutex/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module rwmutex - -go 1.22.0 - -toolchain go1.22.7 diff --git a/cannon/testdata/example/mt-value/go.mod b/cannon/testdata/example/mt-value/go.mod deleted file mode 100644 index 141bfedcc26c8..0000000000000 --- a/cannon/testdata/example/mt-value/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module mtvalue - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/mt-wg/go.mod b/cannon/testdata/example/mt-wg/go.mod deleted file mode 100644 index 9d47ebc46d709..0000000000000 --- a/cannon/testdata/example/mt-wg/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module wg - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/utilscheck/go.mod b/cannon/testdata/example/utilscheck/go.mod deleted file mode 100644 index 724f06cf117c6..0000000000000 --- a/cannon/testdata/example/utilscheck/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module utilscheck - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/utilscheck2/go.mod b/cannon/testdata/example/utilscheck2/go.mod deleted file mode 100644 index ff7e7919f5db3..0000000000000 --- a/cannon/testdata/example/utilscheck2/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module utilscheck2 - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/utilscheck3/go.mod b/cannon/testdata/example/utilscheck3/go.mod deleted file mode 100644 index 37dbb70d2d088..0000000000000 --- a/cannon/testdata/example/utilscheck3/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module utilscheck3 - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/utilscheck4/go.mod b/cannon/testdata/example/utilscheck4/go.mod deleted file mode 100644 index 0efedaec425b1..0000000000000 --- a/cannon/testdata/example/utilscheck4/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module utilscheck4 - -go 1.22.0 - -toolchain go1.22.7 - -require utils v0.0.0 - -replace utils => ../../utils diff --git a/cannon/testdata/example/Makefile b/cannon/testdata/go-1-22/Makefile similarity index 97% rename from cannon/testdata/example/Makefile rename to cannon/testdata/go-1-22/Makefile index 5b0dee96eb9ad..e33cb5567d12e 100644 --- a/cannon/testdata/example/Makefile +++ b/cannon/testdata/go-1-22/Makefile @@ -26,5 +26,5 @@ bin/%.elf: bin # take any ELF and dump it # TODO: currently have the little-endian toolchain, but should use the big-endian one. The -EB compat flag works though. -bin/%.dump: bin/%.elf +bin/%.dump: bin mipsel-linux-gnu-objdump -D --disassembler-options=no-aliases --wide --source -m mips:3000 -EB $(@:%.dump=%.elf) > $@ diff --git a/cannon/testdata/example/hello/go.mod b/cannon/testdata/go-1-22/hello/go.mod similarity index 100% rename from cannon/testdata/example/hello/go.mod rename to cannon/testdata/go-1-22/hello/go.mod diff --git a/cannon/testdata/example/hello/main.go b/cannon/testdata/go-1-22/hello/main.go similarity index 100% rename from cannon/testdata/example/hello/main.go rename to cannon/testdata/go-1-22/hello/main.go diff --git a/cannon/testdata/go-1-23/Makefile b/cannon/testdata/go-1-23/Makefile new file mode 100644 index 0000000000000..e33cb5567d12e --- /dev/null +++ b/cannon/testdata/go-1-23/Makefile @@ -0,0 +1,30 @@ +all: elf + +.PHONY: elf32 +elf32: $(patsubst %/go.mod,bin/%.elf,$(wildcard */go.mod)) + +.PHONY: elf64 +elf64: $(patsubst %/go.mod,bin/%.64.elf,$(wildcard */go.mod)) + +.PHONY: elf +elf: elf32 elf64 + +.PHONY: dump +dump: $(patsubst %/go.mod,bin/%.dump,$(wildcard */go.mod)) + +bin: + mkdir bin + +bin/%.64.elf: bin + cd $(@:bin/%.64.elf=%) && GOOS=linux GOARCH=mips64 GOMIPS64=softfloat go build -o ../$@ . + +# take any directory with a go mod, and build an ELF +# verify output with: readelf -h bin/.elf +# result is mips32, big endian, R3000 +bin/%.elf: bin + cd $(@:bin/%.elf=%) && GOOS=linux GOARCH=mips GOMIPS=softfloat go build -o ../$@ . + +# take any ELF and dump it +# TODO: currently have the little-endian toolchain, but should use the big-endian one. The -EB compat flag works though. +bin/%.dump: bin + mipsel-linux-gnu-objdump -D --disassembler-options=no-aliases --wide --source -m mips:3000 -EB $(@:%.dump=%.elf) > $@ diff --git a/cannon/testdata/example/alloc/go.mod b/cannon/testdata/go-1-23/alloc/go.mod similarity index 62% rename from cannon/testdata/example/alloc/go.mod rename to cannon/testdata/go-1-23/alloc/go.mod index f6b93387d5170..ffca2cb753511 100644 --- a/cannon/testdata/example/alloc/go.mod +++ b/cannon/testdata/go-1-23/alloc/go.mod @@ -1,8 +1,8 @@ module alloc -go 1.22.0 +go 1.23 -toolchain go1.22.7 +toolchain go1.23.8 require github.com/ethereum-optimism/optimism v0.0.0 @@ -11,4 +11,4 @@ require ( golang.org/x/sys v0.30.0 // indirect ) -replace github.com/ethereum-optimism/optimism v0.0.0 => ../../../.. +replace github.com/ethereum-optimism/optimism v0.0.0 => ./../../../.. diff --git a/cannon/testdata/example/alloc/go.sum b/cannon/testdata/go-1-23/alloc/go.sum similarity index 100% rename from cannon/testdata/example/alloc/go.sum rename to cannon/testdata/go-1-23/alloc/go.sum diff --git a/cannon/testdata/example/alloc/main.go b/cannon/testdata/go-1-23/alloc/main.go similarity index 100% rename from cannon/testdata/example/alloc/main.go rename to cannon/testdata/go-1-23/alloc/main.go diff --git a/cannon/testdata/example/claim/go.mod b/cannon/testdata/go-1-23/claim/go.mod similarity index 62% rename from cannon/testdata/example/claim/go.mod rename to cannon/testdata/go-1-23/claim/go.mod index 5c5a9cc9ab259..7639f237b5ff1 100644 --- a/cannon/testdata/example/claim/go.mod +++ b/cannon/testdata/go-1-23/claim/go.mod @@ -1,8 +1,8 @@ module claim -go 1.22.0 +go 1.23 -toolchain go1.22.7 +toolchain go1.23.8 require github.com/ethereum-optimism/optimism v0.0.0 @@ -11,4 +11,4 @@ require ( golang.org/x/sys v0.30.0 // indirect ) -replace github.com/ethereum-optimism/optimism v0.0.0 => ../../../.. +replace github.com/ethereum-optimism/optimism v0.0.0 => ./../../../.. diff --git a/cannon/testdata/example/claim/go.sum b/cannon/testdata/go-1-23/claim/go.sum similarity index 100% rename from cannon/testdata/example/claim/go.sum rename to cannon/testdata/go-1-23/claim/go.sum diff --git a/cannon/testdata/example/claim/main.go b/cannon/testdata/go-1-23/claim/main.go similarity index 100% rename from cannon/testdata/example/claim/main.go rename to cannon/testdata/go-1-23/claim/main.go diff --git a/cannon/testdata/go-1-23/entry/go.mod b/cannon/testdata/go-1-23/entry/go.mod new file mode 100644 index 0000000000000..08eaa304adbfa --- /dev/null +++ b/cannon/testdata/go-1-23/entry/go.mod @@ -0,0 +1,5 @@ +module entry + +go 1.23 + +toolchain go1.23.8 diff --git a/cannon/testdata/example/entry/main.go b/cannon/testdata/go-1-23/entry/main.go similarity index 100% rename from cannon/testdata/example/entry/main.go rename to cannon/testdata/go-1-23/entry/main.go diff --git a/cannon/testdata/go-1-23/hello/go.mod b/cannon/testdata/go-1-23/hello/go.mod new file mode 100644 index 0000000000000..881a7e8fd2acf --- /dev/null +++ b/cannon/testdata/go-1-23/hello/go.mod @@ -0,0 +1,5 @@ +module hello + +go 1.23 + +toolchain go1.23.8 diff --git a/cannon/testdata/go-1-23/hello/main.go b/cannon/testdata/go-1-23/hello/main.go new file mode 100644 index 0000000000000..bcf1c75fc3f55 --- /dev/null +++ b/cannon/testdata/go-1-23/hello/main.go @@ -0,0 +1,7 @@ +package main + +import "os" + +func main() { + _, _ = os.Stdout.Write([]byte("hello world!\n")) +} diff --git a/cannon/testdata/example/mt-atomic/atomic_test_copy.go b/cannon/testdata/go-1-23/mt-atomic/atomic_test_copy.go similarity index 100% rename from cannon/testdata/example/mt-atomic/atomic_test_copy.go rename to cannon/testdata/go-1-23/mt-atomic/atomic_test_copy.go diff --git a/cannon/testdata/go-1-23/mt-atomic/go.mod b/cannon/testdata/go-1-23/mt-atomic/go.mod new file mode 100644 index 0000000000000..5d3801f5f8a90 --- /dev/null +++ b/cannon/testdata/go-1-23/mt-atomic/go.mod @@ -0,0 +1,9 @@ +module atomic + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/mt-atomic/main.go b/cannon/testdata/go-1-23/mt-atomic/main.go similarity index 100% rename from cannon/testdata/example/mt-atomic/main.go rename to cannon/testdata/go-1-23/mt-atomic/main.go diff --git a/cannon/testdata/go-1-23/mt-cond/go.mod b/cannon/testdata/go-1-23/mt-cond/go.mod new file mode 100644 index 0000000000000..7353e7884e990 --- /dev/null +++ b/cannon/testdata/go-1-23/mt-cond/go.mod @@ -0,0 +1,5 @@ +module cond + +go 1.23 + +toolchain go1.23.8 diff --git a/cannon/testdata/example/mt-cond/main.go b/cannon/testdata/go-1-23/mt-cond/main.go similarity index 100% rename from cannon/testdata/example/mt-cond/main.go rename to cannon/testdata/go-1-23/mt-cond/main.go diff --git a/cannon/testdata/go-1-23/mt-general/go.mod b/cannon/testdata/go-1-23/mt-general/go.mod new file mode 100644 index 0000000000000..a1e87038bfe9b --- /dev/null +++ b/cannon/testdata/go-1-23/mt-general/go.mod @@ -0,0 +1,5 @@ +module mtgeneral + +go 1.23 + +toolchain go1.23.8 diff --git a/cannon/testdata/example/mt-general/main.go b/cannon/testdata/go-1-23/mt-general/main.go similarity index 100% rename from cannon/testdata/example/mt-general/main.go rename to cannon/testdata/go-1-23/mt-general/main.go diff --git a/cannon/testdata/go-1-23/mt-map/go.mod b/cannon/testdata/go-1-23/mt-map/go.mod new file mode 100644 index 0000000000000..e969e4587632e --- /dev/null +++ b/cannon/testdata/go-1-23/mt-map/go.mod @@ -0,0 +1,9 @@ +module map + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/mt-map/main.go b/cannon/testdata/go-1-23/mt-map/main.go similarity index 100% rename from cannon/testdata/example/mt-map/main.go rename to cannon/testdata/go-1-23/mt-map/main.go diff --git a/cannon/testdata/example/mt-map/map_reference_test_copy.go b/cannon/testdata/go-1-23/mt-map/map_reference_test_copy.go similarity index 100% rename from cannon/testdata/example/mt-map/map_reference_test_copy.go rename to cannon/testdata/go-1-23/mt-map/map_reference_test_copy.go diff --git a/cannon/testdata/example/mt-map/map_test_copy.go b/cannon/testdata/go-1-23/mt-map/map_test_copy.go similarity index 100% rename from cannon/testdata/example/mt-map/map_test_copy.go rename to cannon/testdata/go-1-23/mt-map/map_test_copy.go diff --git a/cannon/testdata/go-1-23/mt-mutex/go.mod b/cannon/testdata/go-1-23/mt-mutex/go.mod new file mode 100644 index 0000000000000..dde37e2bb768c --- /dev/null +++ b/cannon/testdata/go-1-23/mt-mutex/go.mod @@ -0,0 +1,9 @@ +module mutex + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/mt-mutex/main.go b/cannon/testdata/go-1-23/mt-mutex/main.go similarity index 100% rename from cannon/testdata/example/mt-mutex/main.go rename to cannon/testdata/go-1-23/mt-mutex/main.go diff --git a/cannon/testdata/example/mt-mutex/mutex_test_copy.go b/cannon/testdata/go-1-23/mt-mutex/mutex_test_copy.go similarity index 100% rename from cannon/testdata/example/mt-mutex/mutex_test_copy.go rename to cannon/testdata/go-1-23/mt-mutex/mutex_test_copy.go diff --git a/cannon/testdata/example/mt-mutex/runtime.go b/cannon/testdata/go-1-23/mt-mutex/runtime.go similarity index 100% rename from cannon/testdata/example/mt-mutex/runtime.go rename to cannon/testdata/go-1-23/mt-mutex/runtime.go diff --git a/cannon/testdata/go-1-23/mt-once/go.mod b/cannon/testdata/go-1-23/mt-once/go.mod new file mode 100644 index 0000000000000..ccb539de66f61 --- /dev/null +++ b/cannon/testdata/go-1-23/mt-once/go.mod @@ -0,0 +1,5 @@ +module once + +go 1.23 + +toolchain go1.23.8 diff --git a/cannon/testdata/example/mt-once/main.go b/cannon/testdata/go-1-23/mt-once/main.go similarity index 100% rename from cannon/testdata/example/mt-once/main.go rename to cannon/testdata/go-1-23/mt-once/main.go diff --git a/cannon/testdata/go-1-23/mt-oncefunc/go.mod b/cannon/testdata/go-1-23/mt-oncefunc/go.mod new file mode 100644 index 0000000000000..80229690276fc --- /dev/null +++ b/cannon/testdata/go-1-23/mt-oncefunc/go.mod @@ -0,0 +1,9 @@ +module oncefunc + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/mt-oncefunc/main.go b/cannon/testdata/go-1-23/mt-oncefunc/main.go similarity index 100% rename from cannon/testdata/example/mt-oncefunc/main.go rename to cannon/testdata/go-1-23/mt-oncefunc/main.go diff --git a/cannon/testdata/example/mt-oncefunc/oncefunc_test_copy.go b/cannon/testdata/go-1-23/mt-oncefunc/oncefunc_test_copy.go similarity index 100% rename from cannon/testdata/example/mt-oncefunc/oncefunc_test_copy.go rename to cannon/testdata/go-1-23/mt-oncefunc/oncefunc_test_copy.go diff --git a/cannon/testdata/example/mt-pool/export_test_copy.go b/cannon/testdata/go-1-23/mt-pool/export_test_copy.go similarity index 100% rename from cannon/testdata/example/mt-pool/export_test_copy.go rename to cannon/testdata/go-1-23/mt-pool/export_test_copy.go diff --git a/cannon/testdata/go-1-23/mt-pool/go.mod b/cannon/testdata/go-1-23/mt-pool/go.mod new file mode 100644 index 0000000000000..c30c2b51a58f1 --- /dev/null +++ b/cannon/testdata/go-1-23/mt-pool/go.mod @@ -0,0 +1,9 @@ +module pool + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/mt-pool/main.go b/cannon/testdata/go-1-23/mt-pool/main.go similarity index 100% rename from cannon/testdata/example/mt-pool/main.go rename to cannon/testdata/go-1-23/mt-pool/main.go diff --git a/cannon/testdata/example/mt-pool/pool_test_copy.go b/cannon/testdata/go-1-23/mt-pool/pool_test_copy.go similarity index 100% rename from cannon/testdata/example/mt-pool/pool_test_copy.go rename to cannon/testdata/go-1-23/mt-pool/pool_test_copy.go diff --git a/cannon/testdata/example/mt-pool/poolqueue_copy.go b/cannon/testdata/go-1-23/mt-pool/poolqueue_copy.go similarity index 100% rename from cannon/testdata/example/mt-pool/poolqueue_copy.go rename to cannon/testdata/go-1-23/mt-pool/poolqueue_copy.go diff --git a/cannon/testdata/example/mt-pool/runtime.go b/cannon/testdata/go-1-23/mt-pool/runtime.go similarity index 100% rename from cannon/testdata/example/mt-pool/runtime.go rename to cannon/testdata/go-1-23/mt-pool/runtime.go diff --git a/cannon/testdata/go-1-23/mt-rwmutex/go.mod b/cannon/testdata/go-1-23/mt-rwmutex/go.mod new file mode 100644 index 0000000000000..6777f195c257a --- /dev/null +++ b/cannon/testdata/go-1-23/mt-rwmutex/go.mod @@ -0,0 +1,5 @@ +module rwmutex + +go 1.23 + +toolchain go1.23.8 diff --git a/cannon/testdata/example/mt-rwmutex/main.go b/cannon/testdata/go-1-23/mt-rwmutex/main.go similarity index 100% rename from cannon/testdata/example/mt-rwmutex/main.go rename to cannon/testdata/go-1-23/mt-rwmutex/main.go diff --git a/cannon/testdata/go-1-23/mt-value/go.mod b/cannon/testdata/go-1-23/mt-value/go.mod new file mode 100644 index 0000000000000..78ce2b1f8283b --- /dev/null +++ b/cannon/testdata/go-1-23/mt-value/go.mod @@ -0,0 +1,9 @@ +module mtvalue + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/mt-value/main.go b/cannon/testdata/go-1-23/mt-value/main.go similarity index 100% rename from cannon/testdata/example/mt-value/main.go rename to cannon/testdata/go-1-23/mt-value/main.go diff --git a/cannon/testdata/example/mt-value/value_test_copy.go b/cannon/testdata/go-1-23/mt-value/value_test_copy.go similarity index 100% rename from cannon/testdata/example/mt-value/value_test_copy.go rename to cannon/testdata/go-1-23/mt-value/value_test_copy.go diff --git a/cannon/testdata/go-1-23/mt-wg/go.mod b/cannon/testdata/go-1-23/mt-wg/go.mod new file mode 100644 index 0000000000000..7f34ebf77f53f --- /dev/null +++ b/cannon/testdata/go-1-23/mt-wg/go.mod @@ -0,0 +1,9 @@ +module wg + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/mt-wg/main.go b/cannon/testdata/go-1-23/mt-wg/main.go similarity index 100% rename from cannon/testdata/example/mt-wg/main.go rename to cannon/testdata/go-1-23/mt-wg/main.go diff --git a/cannon/testdata/example/mt-wg/waitgroup_test_copy.go b/cannon/testdata/go-1-23/mt-wg/waitgroup_test_copy.go similarity index 100% rename from cannon/testdata/example/mt-wg/waitgroup_test_copy.go rename to cannon/testdata/go-1-23/mt-wg/waitgroup_test_copy.go diff --git a/cannon/testdata/go-1-23/utilscheck/go.mod b/cannon/testdata/go-1-23/utilscheck/go.mod new file mode 100644 index 0000000000000..276f2c6b8aa31 --- /dev/null +++ b/cannon/testdata/go-1-23/utilscheck/go.mod @@ -0,0 +1,9 @@ +module utilscheck + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/utilscheck/main.go b/cannon/testdata/go-1-23/utilscheck/main.go similarity index 100% rename from cannon/testdata/example/utilscheck/main.go rename to cannon/testdata/go-1-23/utilscheck/main.go diff --git a/cannon/testdata/go-1-23/utilscheck2/go.mod b/cannon/testdata/go-1-23/utilscheck2/go.mod new file mode 100644 index 0000000000000..dcf2087cb9285 --- /dev/null +++ b/cannon/testdata/go-1-23/utilscheck2/go.mod @@ -0,0 +1,9 @@ +module utilscheck2 + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/utilscheck2/main.go b/cannon/testdata/go-1-23/utilscheck2/main.go similarity index 100% rename from cannon/testdata/example/utilscheck2/main.go rename to cannon/testdata/go-1-23/utilscheck2/main.go diff --git a/cannon/testdata/go-1-23/utilscheck3/go.mod b/cannon/testdata/go-1-23/utilscheck3/go.mod new file mode 100644 index 0000000000000..54170909ca03d --- /dev/null +++ b/cannon/testdata/go-1-23/utilscheck3/go.mod @@ -0,0 +1,9 @@ +module utilscheck3 + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/utilscheck3/main.go b/cannon/testdata/go-1-23/utilscheck3/main.go similarity index 100% rename from cannon/testdata/example/utilscheck3/main.go rename to cannon/testdata/go-1-23/utilscheck3/main.go diff --git a/cannon/testdata/go-1-23/utilscheck4/go.mod b/cannon/testdata/go-1-23/utilscheck4/go.mod new file mode 100644 index 0000000000000..a17294cd28fa4 --- /dev/null +++ b/cannon/testdata/go-1-23/utilscheck4/go.mod @@ -0,0 +1,9 @@ +module utilscheck4 + +go 1.23 + +toolchain go1.23.8 + +require utils v0.0.0 + +replace utils => ./../../utils diff --git a/cannon/testdata/example/utilscheck4/main.go b/cannon/testdata/go-1-23/utilscheck4/main.go similarity index 100% rename from cannon/testdata/example/utilscheck4/main.go rename to cannon/testdata/go-1-23/utilscheck4/main.go diff --git a/op-deployer/pkg/deployer/pipeline/dispute_games.go b/op-deployer/pkg/deployer/pipeline/dispute_games.go index e98dc084d1ccd..1452c952da5af 100644 --- a/op-deployer/pkg/deployer/pipeline/dispute_games.go +++ b/op-deployer/pkg/deployer/pipeline/dispute_games.go @@ -84,14 +84,9 @@ func deployDisputeGame( return fmt.Errorf("failed to deploy Alphabet VM: %w", err) } vmAddr = out.AlphabetVM - case state.VMTypeCannon1, state.VMTypeCannon2: - mipsVersion := 1 - if game.VMType == state.VMTypeCannon2 { - mipsVersion = 6 - } - + case state.VMTypeCannon1, state.VMTypeCannon2, state.VMTypeCannon6, state.VMTypeCannon7: out, err := opcm.DeployMIPS(env.L1ScriptHost, opcm.DeployMIPSInput{ - MipsVersion: uint64(mipsVersion), + MipsVersion: game.VMType.MipsVersion(), PreimageOracle: oracleAddr, }) if err != nil { diff --git a/op-deployer/pkg/deployer/state/chain_intent.go b/op-deployer/pkg/deployer/state/chain_intent.go index bba50035bf75c..df8b5e7bb26de 100644 --- a/op-deployer/pkg/deployer/state/chain_intent.go +++ b/op-deployer/pkg/deployer/state/chain_intent.go @@ -14,10 +14,26 @@ type VMType string const ( VMTypeAlphabet = "ALPHABET" - VMTypeCannon1 = "CANNON1" - VMTypeCannon2 = "CANNON2" + VMTypeCannon1 = "CANNON1" // Legacy: corresponds to 32-bit MIPS VM + VMTypeCannon2 = "CANNON2" // Legacy: corresponds to 64-bit MIPS VM StateVersion 6 + VMTypeCannon6 = "CANNON6" // Corresponds to VM State Version 6: https://github.com/ethereum-optimism/optimism/blob/4c05241bc534ae5837007c32995fc62f3dd059b6/cannon/mipsevm/versions/version.go#L25-L25 + VMTypeCannon7 = "CANNON7" // Corresponds to VM State Version 7: https://github.com/ethereum-optimism/optimism/blob/4c05241bc534ae5837007c32995fc62f3dd059b6/cannon/mipsevm/versions/version.go#L27-L27 ) +func (v VMType) MipsVersion() uint64 { + switch v { + case VMTypeCannon1: + return 1 + case VMTypeCannon2, VMTypeCannon6: + return 6 + case VMTypeCannon7: + return 7 + default: + // Not a mips VM - return empty value + return 0 + } +} + type ChainProofParams struct { DisputeGameType uint32 `json:"respectedGameType" toml:"respectedGameType"` DisputeAbsolutePrestate common.Hash `json:"faultGameAbsolutePrestate" toml:"faultGameAbsolutePrestate"` diff --git a/op-e2e/config/init.go b/op-e2e/config/init.go index 148e0625195fb..4541517d7f316 100644 --- a/op-e2e/config/init.go +++ b/op-e2e/config/init.go @@ -49,10 +49,11 @@ const ( type AllocType string const ( - AllocTypeStandard AllocType = "standard" - AllocTypeAltDA AllocType = "alt-da" - AllocTypeL2OO AllocType = "l2oo" - AllocTypeMTCannon AllocType = "mt-cannon" + AllocTypeStandard AllocType = "standard" + AllocTypeAltDA AllocType = "alt-da" + AllocTypeL2OO AllocType = "l2oo" + AllocTypeMTCannon AllocType = "mt-cannon" + AllocTypeMTCannonNext AllocType = "mt-cannon-next" DefaultAllocType = AllocTypeStandard ) @@ -66,14 +67,14 @@ func (a AllocType) Check() error { func (a AllocType) UsesProofs() bool { switch a { - case AllocTypeStandard, AllocTypeMTCannon, AllocTypeAltDA: + case AllocTypeStandard, AllocTypeMTCannon, AllocTypeMTCannonNext, AllocTypeAltDA: return true default: return false } } -var allocTypes = []AllocType{AllocTypeStandard, AllocTypeAltDA, AllocTypeL2OO, AllocTypeMTCannon} +var allocTypes = []AllocType{AllocTypeStandard, AllocTypeAltDA, AllocTypeL2OO, AllocTypeMTCannon, AllocTypeMTCannonNext} var ( // All of the following variables are set in the init function @@ -507,7 +508,9 @@ func decompressGzipJSON(p string, thing any) { func cannonVMType(allocType AllocType) state.VMType { if allocType == AllocTypeMTCannon { - return state.VMTypeCannon2 + return state.VMTypeCannon6 + } else if allocType == AllocTypeMTCannonNext { + return state.VMTypeCannon7 } return state.VMTypeCannon1 } @@ -517,8 +520,10 @@ type prestateFile struct { } var cannonPrestateMT common.Hash +var cannonPrestateMTNext common.Hash var cannonPrestateST common.Hash var cannonPrestateMTOnce sync.Once +var cannonPrestateMTNextOnce sync.Once var cannonPrestateSTOnce sync.Once func cannonPrestate(monorepoRoot string, allocType AllocType) common.Hash { @@ -526,14 +531,21 @@ func cannonPrestate(monorepoRoot string, allocType AllocType) common.Hash { var once *sync.Once var cacheVar *common.Hash - if cannonVMType(allocType) == state.VMTypeCannon1 { + cannonVmType := cannonVMType(allocType) + if cannonVmType == state.VMTypeCannon1 { filename = "prestate-proof.json" once = &cannonPrestateSTOnce cacheVar = &cannonPrestateST - } else { + } else if cannonVmType == state.VMTypeCannon2 || cannonVmType == state.VMTypeCannon6 { filename = "prestate-proof-mt64.json" once = &cannonPrestateMTOnce cacheVar = &cannonPrestateMT + } else if cannonVmType == state.VMTypeCannon7 { + filename = "prestate-proof-mt64Next.json" + once = &cannonPrestateMTNextOnce + cacheVar = &cannonPrestateMTNext + } else { + panic("Unsupported cannon VM type: " + cannonVmType) } once.Do(func() { diff --git a/op-e2e/e2eutils/challenger/helper.go b/op-e2e/e2eutils/challenger/helper.go index 5c34d2adace6c..44ab69be2750d 100644 --- a/op-e2e/e2eutils/challenger/helper.go +++ b/op-e2e/e2eutils/challenger/helper.go @@ -35,9 +35,10 @@ import ( type PrestateVariant string const ( - STCannonVariant PrestateVariant = "" - MTCannonVariant PrestateVariant = "mt64" - InteropVariant PrestateVariant = "interop" + STCannonVariant PrestateVariant = "" + MTCannonVariant PrestateVariant = "mt64" + MTCannonNextVariant PrestateVariant = "mt64Next" + InteropVariant PrestateVariant = "interop" ) type EndpointProvider interface { diff --git a/op-e2e/faultproofs/cannon_benchmark_test.go b/op-e2e/faultproofs/cannon_benchmark_test.go index 57b5f1d83e70e..04b0ec76e1ea7 100644 --- a/op-e2e/faultproofs/cannon_benchmark_test.go +++ b/op-e2e/faultproofs/cannon_benchmark_test.go @@ -11,7 +11,6 @@ import ( "testing" "time" - op_e2e "github.com/ethereum-optimism/optimism/op-e2e" "github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" @@ -33,18 +32,12 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testlog" ) -func TestBenchmarkCannonFPP_Standard(t *testing.T) { - testBenchmarkCannonFPP(t, config.AllocTypeStandard) -} - -func TestBenchmarkCannonFPP_Multithreaded(t *testing.T) { - testBenchmarkCannonFPP(t, config.AllocTypeMTCannon) +func TestBenchmarkCannonFPP(t *testing.T) { + t.Skip("TODO(client-pod#906): Compare total witness size for assertions against pages allocated by the VM") + RunTestAcrossVmTypes(t, testBenchmarkCannonFPP) } func testBenchmarkCannonFPP(t *testing.T, allocType config.AllocType) { - t.Skip("TODO(client-pod#906): Compare total witness size for assertions against pages allocated by the VM") - - op_e2e.InitParallel(t, op_e2e.UsesCannon) ctx := context.Background() cfg := e2esys.DefaultSystemConfig(t, e2esys.WithAllocType(allocType)) // We don't need a verifier - just the sequencer is enough diff --git a/op-e2e/faultproofs/output_cannon_test.go b/op-e2e/faultproofs/output_cannon_test.go index 7a9f5f9e808db..5167c0edd728c 100644 --- a/op-e2e/faultproofs/output_cannon_test.go +++ b/op-e2e/faultproofs/output_cannon_test.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" - op_e2e "github.com/ethereum-optimism/optimism/op-e2e" "github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame" @@ -20,16 +19,11 @@ import ( "github.com/stretchr/testify/require" ) -func TestOutputCannonGame_Standard(t *testing.T) { - testOutputCannonGame(t, config.AllocTypeStandard) -} - -func TestOutputCannonGame_Multithreaded(t *testing.T) { - testOutputCannonGame(t, config.AllocTypeMTCannon) +func TestOutputCannonGame(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonGame) } func testOutputCannonGame(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -40,17 +34,12 @@ func testOutputCannonGame(t *testing.T, allocType config.AllocType) { testCannonGame(t, ctx, arena, &game.SplitGameHelper) } -func TestOutputCannon_ChallengeAllZeroClaim_Standard(t *testing.T) { - testOutputCannonChallengeAllZeroClaim(t, config.AllocTypeStandard) -} - -func TestOutputCannon_ChallengeAllZeroClaim_Multithreaded(t *testing.T) { - testOutputCannonChallengeAllZeroClaim(t, config.AllocTypeMTCannon) +func TestOutputCannon_ChallengeAllZeroClaim(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonChallengeAllZeroClaim) } func testOutputCannonChallengeAllZeroClaim(t *testing.T, allocType config.AllocType) { // The dishonest actor always posts claims with all zeros. - op_e2e.InitParallel(t, op_e2e.UsesCannon) ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -61,112 +50,87 @@ func testOutputCannonChallengeAllZeroClaim(t *testing.T, allocType config.AllocT testCannonChallengeAllZeroClaim(t, ctx, arena, &game.SplitGameHelper) } -func TestOutputCannon_PublishCannonRootClaim_Standard(t *testing.T) { - testOutputCannonPublishCannonRootClaim(t, config.AllocTypeStandard) -} - -func TestOutputCannon_PublishCannonRootClaim_Multithreaded(t *testing.T) { - testOutputCannonPublishCannonRootClaim(t, config.AllocTypeMTCannon) -} - -func testOutputCannonPublishCannonRootClaim(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - tests := []struct { +func TestOutputCannon_PublishCannonRootClaim(t *testing.T) { + type TestCase struct { disputeL2BlockNumber uint64 - }{ + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("Dispute_%v_%v", test.disputeL2BlockNumber, vm) + } + tests := []TestCase{ {7}, // Post-state output root is invalid {8}, // Post-state output root is valid } - for _, test := range tests { - test := test - t.Run(fmt.Sprintf("Dispute_%v", test.disputeL2BlockNumber), func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - - ctx := context.Background() - sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) - disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) - game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", test.disputeL2BlockNumber, common.Hash{0x01}) - game.DisputeLastBlock(ctx) - game.LogGameData(ctx) - - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { + ctx := context.Background() + sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) - splitDepth := game.SplitDepth(ctx) - game.WaitForClaimAtDepth(ctx, splitDepth+1) - }) - } -} + disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) + game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", test.disputeL2BlockNumber, common.Hash{0x01}) + game.DisputeLastBlock(ctx) + game.LogGameData(ctx) -func TestOutputCannonDisputeGame_Standard(t *testing.T) { - testOutputCannonDisputeGame(t, config.AllocTypeStandard) -} + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) -func TestOutputCannonDisputeGame_Multithreaded(t *testing.T) { - testOutputCannonDisputeGame(t, config.AllocTypeMTCannon) + splitDepth := game.SplitDepth(ctx) + game.WaitForClaimAtDepth(ctx, splitDepth+1) + }, WithTestName(testName)) } -func testOutputCannonDisputeGame(t *testing.T, allocType config.AllocType) { - - op_e2e.InitParallel(t, op_e2e.UsesCannon) - tests := []struct { +func TestOutputCannonDisputeGame(t *testing.T) { + type TestCase struct { name string defendClaimDepth types.Depth - }{ + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("%v-%v", test.name, vm) + } + tests := []TestCase{ {"StepFirst", 0}, {"StepMiddle", 28}, {"StepInExtension", 1}, } - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - - ctx := context.Background() - sys, l1Client := StartFaultDisputeSystem(t, WithAllocType(allocType)) - t.Cleanup(sys.Close) - - disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) - game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa}) - require.NotNil(t, game) - game.LogGameData(ctx) - - outputClaim := game.DisputeLastBlock(ctx) - splitDepth := game.SplitDepth(ctx) - - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) - - game.DefendClaim( - ctx, - outputClaim, - func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper { - if claim.Depth()+1 == splitDepth+test.defendClaimDepth { - return claim.Defend(ctx, common.Hash{byte(claim.Depth())}) - } else { - return claim.Attack(ctx, common.Hash{byte(claim.Depth())}) - } - }) - - sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, l1Client)) - - game.LogGameData(ctx) - game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) - }) - } -} -func TestOutputCannonDefendStep_Standard(t *testing.T) { - testOutputCannonDefendStep(t, config.AllocTypeStandard) + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { + ctx := context.Background() + sys, l1Client := StartFaultDisputeSystem(t, WithAllocType(allocType)) + t.Cleanup(sys.Close) + + disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) + game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa}) + require.NotNil(t, game) + game.LogGameData(ctx) + + outputClaim := game.DisputeLastBlock(ctx) + splitDepth := game.SplitDepth(ctx) + + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + + game.DefendClaim( + ctx, + outputClaim, + func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper { + if claim.Depth()+1 == splitDepth+test.defendClaimDepth { + return claim.Defend(ctx, common.Hash{byte(claim.Depth())}) + } else { + return claim.Attack(ctx, common.Hash{byte(claim.Depth())}) + } + }) + + sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, l1Client)) + + game.LogGameData(ctx) + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) + }, WithTestName(testName)) } -func TestOutputCannonDefendStep_Multithreaded(t *testing.T) { - testOutputCannonDefendStep(t, config.AllocTypeMTCannon) +func TestOutputCannonDefendStep(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonDefendStep) } func testOutputCannonDefendStep(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -177,17 +141,11 @@ func testOutputCannonDefendStep(t *testing.T, allocType config.AllocType) { testCannonDefendStep(t, ctx, arena, &game.SplitGameHelper) } -func TestOutputCannonStepWithLargePreimage_Standard(t *testing.T) { - testOutputCannonStepWithLargePreimage(t, config.AllocTypeStandard) -} - -func TestOutputCannonStepWithLargePreimage_Multithreaded(t *testing.T) { - testOutputCannonStepWithLargePreimage(t, config.AllocTypeMTCannon) +func TestOutputCannonStepWithLargePreimage(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonStepWithLargePreimage) } func testOutputCannonStepWithLargePreimage(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithBatcherStopped(), WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -227,101 +185,104 @@ func testOutputCannonStepWithLargePreimage(t *testing.T, allocType config.AllocT // So we don't waste time resolving the game - that's tested elsewhere. } -func TestOutputCannonStepWithPreimage_Standard(t *testing.T) { - testOutputCannonStepWithPreimage(t, config.AllocTypeStandard) -} +func TestOutputCannonStepWithPreimage_nonExistingPreimage(t *testing.T) { + type TestCase struct { + name string + preimageType oppreimage.KeyType + opts []disputegame.FindPreimageStepOpt + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("%v-%v", test.name, vm) + } + tests := []TestCase{ + {name: "keccak", preimageType: oppreimage.Keccak256KeyType}, + // Sha256 preimages are relatively rare, so allow fallback to even step to avoid flakes + {name: "sha256", preimageType: oppreimage.Sha256KeyType, opts: []disputegame.FindPreimageStepOpt{disputegame.AllowEvenFallback()}}, + } -func TestOutputCannonStepWithPreimage_Multithreaded(t *testing.T) { - testOutputCannonStepWithPreimage(t, config.AllocTypeMTCannon) + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, testcase TestCase) { + conf := utils.PreimageOptConfigForType(oppreimage.Keccak256KeyType) + testPreimageStep(t, allocType, conf, false, testcase.opts...) + }, WithTestName(testName)) } -func testOutputCannonStepWithPreimage(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - testPreimageStep := func(t *testing.T, preimageOptConfig utils.PreimageOptConfig, preloadPreimage bool, opts ...disputegame.FindPreimageStepOpt) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - - ctx := context.Background() - sys, _ := StartFaultDisputeSystem(t, WithBlobBatches(), WithAllocType(allocType)) - t.Cleanup(sys.Close) - - disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) - game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa}) - require.NotNil(t, game) - outputRootClaim := game.DisputeLastBlock(ctx) - game.LogGameData(ctx) - - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) - - // Wait for the honest challenger to dispute the outputRootClaim. This creates a root of an execution game that we challenge by coercing - // a step at a preimage trace index. - outputRootClaim = outputRootClaim.WaitForCounterClaim(ctx) - - // Now the honest challenger is positioned as the defender of the execution game - // We then move to challenge it to induce a preimage load - // Check that the preimage is loaded into the oracle with data matching our expectation - getExpectedData := func(p *types.PreimageOracleData) (bool, [32]byte) { return true, game.GetPreimageAtOffset(p) } - preimageLoadCheck := game.CreateStepPreimageLoadStrictCheck(ctx, getExpectedData) - // We need the honest challenger to step-defend the STF from A -> B such that A loads the preimage - // The ChallengeToPreimageLoadAtTarget method will induce a step-defend on odd numbered trace index from the honest challenger. - providerFunc := game.NewMemoizedCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) - step := game.FindOddStepForPreimageLoad(ctx, providerFunc, preimageOptConfig, opts...) - game.ChallengeToPreimageLoadAtTarget(ctx, providerFunc, step, preimageLoadCheck, preloadPreimage) - // The above method already verified the image was uploaded and step called successfully - // So we don't waste time resolving the game - that's tested elsewhere. - - // Finally, validate that we can manually invoke step at this point in the game and produce the expected post-state - game.VerifyPreimageAtTarget(ctx, providerFunc, step, game.GetOracleKeyPrefixValidator(preimageOptConfig.KeyPrefix), false) +func TestOutputCannonStepWithPreimage_nonExistingBlobPreimage(t *testing.T) { + type TestCase struct { + blobOffset uint32 + blobSkipCount int + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("non-existing preimage-blob-%v skip-%v [%v]", strconv.Itoa(int(test.blobOffset)), test.blobSkipCount, vm) } - t.Run("non-existing preimage-keccak", func(t *testing.T) { - conf := utils.PreimageOptConfigForType(oppreimage.Keccak256KeyType) - testPreimageStep(t, conf, false) - }) - t.Run("non-existing preimage-sha256", func(t *testing.T) { - conf := utils.PreimageOptConfigForType(oppreimage.Sha256KeyType) - // Sha256 preimages are relatively rare, so allow fallback to even step to avoid flakes - testPreimageStep(t, conf, false, disputegame.AllowEvenFallback()) - }) - - // Include non-zero offset to induce a load of the part of the preimage after the length prefix + testCases := make([]TestCase, 0) blobOffsets := []uint32{0, 8, 16, 24, 32} skipCounts := []int{0, 1, 2, 11} for _, offset := range blobOffsets { for _, skip := range skipCounts { - testName := fmt.Sprintf("non-existing preimage-blob-%v skip-%v", strconv.Itoa(int(offset)), skip) - t.Run(testName, func(t *testing.T) { - conf := utils.PreimageOptConfigForType(oppreimage.BlobKeyType) - conf.Offset = offset - - // In order to target non-zero blob field indices, skip some preimage load steps. - // Because field elements are retrieved sequentially, this should ensure we advance to - // a field element at an index >= skip - testPreimageStep(t, conf, false, disputegame.SkipNPreimageLoads(skip)) - }) + testCases = append(testCases, TestCase{blobOffset: offset, blobSkipCount: skip}) } } + RunTestsAcrossVmTypes(t, testCases, func(t *testing.T, allocType config.AllocType, testcase TestCase) { + conf := utils.PreimageOptConfigForType(oppreimage.BlobKeyType) + conf.Offset = testcase.blobOffset + + // In order to target non-zero blob field indices, skip some preimage load steps. + // Because field elements are retrieved sequentially, this should ensure we advance to + // a field element at an index >= skip + testPreimageStep(t, allocType, conf, false, disputegame.SkipNPreimageLoads(testcase.blobSkipCount)) + }, WithTestName(testName)) +} + +func TestOutputCannonStepWithPreimage_existingPreimage(t *testing.T) { // Only test pre-existing images with one type to save runtime - t.Run("preimage already exists", func(t *testing.T) { + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { conf := utils.PreimageOptConfigForType(oppreimage.Keccak256KeyType) - testPreimageStep(t, conf, true) + testPreimageStep(t, allocType, conf, true) }) } -func TestOutputCannonStepWithKZGPointEvaluation_Standard(t *testing.T) { - testOutputCannonStepWithKzgPointEvaluation(t, config.AllocTypeStandard) +func testPreimageStep(t *testing.T, allocType config.AllocType, preimageOptConfig utils.PreimageOptConfig, preloadPreimage bool, opts ...disputegame.FindPreimageStepOpt) { + ctx := context.Background() + sys, _ := StartFaultDisputeSystem(t, WithBlobBatches(), WithAllocType(allocType)) + t.Cleanup(sys.Close) + + disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) + game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa}) + require.NotNil(t, game) + outputRootClaim := game.DisputeLastBlock(ctx) + game.LogGameData(ctx) + + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + + // Wait for the honest challenger to dispute the outputRootClaim. This creates a root of an execution game that we challenge by coercing + // a step at a preimage trace index. + outputRootClaim = outputRootClaim.WaitForCounterClaim(ctx) + + // Now the honest challenger is positioned as the defender of the execution game + // We then move to challenge it to induce a preimage load + // Check that the preimage is loaded into the oracle with data matching our expectation + getExpectedData := func(p *types.PreimageOracleData) (bool, [32]byte) { return true, game.GetPreimageAtOffset(p) } + preimageLoadCheck := game.CreateStepPreimageLoadStrictCheck(ctx, getExpectedData) + // We need the honest challenger to step-defend the STF from A -> B such that A loads the preimage + // The ChallengeToPreimageLoadAtTarget method will induce a step-defend on odd numbered trace index from the honest challenger. + providerFunc := game.NewMemoizedCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + step := game.FindOddStepForPreimageLoad(ctx, providerFunc, preimageOptConfig, opts...) + game.ChallengeToPreimageLoadAtTarget(ctx, providerFunc, step, preimageLoadCheck, preloadPreimage) + // The above method already verified the image was uploaded and step called successfully + // So we don't waste time resolving the game - that's tested elsewhere. + + // Finally, validate that we can manually invoke step at this point in the game and produce the expected post-state + game.VerifyPreimageAtTarget(ctx, providerFunc, step, game.GetOracleKeyPrefixValidator(preimageOptConfig.KeyPrefix), false) } -func TestOutputCannonStepWithKZGPointEvaluation_Multithreaded(t *testing.T) { - testOutputCannonStepWithKzgPointEvaluation(t, config.AllocTypeMTCannon) +func TestOutputCannonStepWithKZGPointEvaluation(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonStepWithKzgPointEvaluation) } func testOutputCannonStepWithKzgPointEvaluation(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - testPreimageStep := func(t *testing.T, preloadPreimage bool) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithEcotone(), WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -365,16 +326,11 @@ func testOutputCannonStepWithKzgPointEvaluation(t *testing.T, allocType config.A }) } -func TestOutputCannonProposedOutputRootValid_AttackWithCorrectTrace_Standard(t *testing.T) { - testOutputCannonProposedOutputRootValid_AttackWithCorrectTrace(t, config.AllocTypeStandard) -} - -func TestOutputCannonProposedOutputRootValid_Multithreaded(t *testing.T) { - testOutputCannonProposedOutputRootValid_AttackWithCorrectTrace(t, config.AllocTypeMTCannon) +func TestOutputCannonProposedOutputRootValid(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonProposedOutputRootValid_AttackWithCorrectTrace) } func testOutputCannonProposedOutputRootValid_AttackWithCorrectTrace(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -385,16 +341,11 @@ func testOutputCannonProposedOutputRootValid_AttackWithCorrectTrace(t *testing.T testCannonProposalValid_AttackWithCorrectTrace(t, ctx, arena, &game.SplitGameHelper) } -func TestOutputCannonProposedOutputRootValid_DefendWithCorrectTrace_Standard(t *testing.T) { - testOutputCannonProposedOutputRootValid_DefendWithCorrectTrace(t, config.AllocTypeStandard) -} - -func TestOutputCannonProposedOutputRootValid_DefendWithCorrectTrace_Multithreaded(t *testing.T) { - testOutputCannonProposedOutputRootValid_DefendWithCorrectTrace(t, config.AllocTypeMTCannon) +func TestOutputCannonProposedOutputRootValid_DefendWithCorrectTrace(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonProposedOutputRootValid_DefendWithCorrectTrace) } func testOutputCannonProposedOutputRootValid_DefendWithCorrectTrace(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -405,17 +356,11 @@ func testOutputCannonProposedOutputRootValid_DefendWithCorrectTrace(t *testing.T testCannonProposalValid_DefendWithCorrectTrace(t, ctx, arena, &game.SplitGameHelper) } -func TestOutputCannonPoisonedPostState_Standard(t *testing.T) { - testOutputCannonPoisonedPostState(t, config.AllocTypeStandard) -} - -func TestOutputCannonPoisonedPostState_Multithreaded(t *testing.T) { - testOutputCannonPoisonedPostState(t, config.AllocTypeMTCannon) +func TestOutputCannonPoisonedPostState(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonPoisonedPostState) } func testOutputCannonPoisonedPostState(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -427,17 +372,11 @@ func testOutputCannonPoisonedPostState(t *testing.T, allocType config.AllocType) testCannonPoisonedPostState(t, ctx, arena, &game.SplitGameHelper) } -func TestDisputeOutputRootBeyondProposedBlock_ValidOutputRoot_Standard(t *testing.T) { - testDisputeOutputRootBeyondProposedBlockValidOutputRoot(t, config.AllocTypeStandard) -} - -func TestDisputeOutputRootBeyondProposedBlock_ValidOutputRoot_Multithreaded(t *testing.T) { - testDisputeOutputRootBeyondProposedBlockValidOutputRoot(t, config.AllocTypeMTCannon) +func TestDisputeOutputRootBeyondProposedBlock_ValidOutputRoot(t *testing.T) { + RunTestAcrossVmTypes(t, testDisputeOutputRootBeyondProposedBlockValidOutputRoot) } func testDisputeOutputRootBeyondProposedBlockValidOutputRoot(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -449,17 +388,11 @@ func testDisputeOutputRootBeyondProposedBlockValidOutputRoot(t *testing.T, alloc testDisputeRootBeyondProposedBlockValidOutputRoot(t, ctx, arena, &game.SplitGameHelper) } -func TestDisputeOutputRootBeyondProposedBlock_InvalidOutputRoot_Standard(t *testing.T) { - testDisputeOutputRootBeyondProposedBlockInvalidOutputRoot(t, config.AllocTypeStandard) -} - -func TestDisputeOutputRootBeyondProposedBlock_InvalidOutputRoot_Multithreaded(t *testing.T) { - testDisputeOutputRootBeyondProposedBlockInvalidOutputRoot(t, config.AllocTypeMTCannon) +func TestDisputeOutputRootBeyondProposedBlock_InvalidOutputRoot(t *testing.T) { + RunTestAcrossVmTypes(t, testDisputeOutputRootBeyondProposedBlockInvalidOutputRoot) } func testDisputeOutputRootBeyondProposedBlockInvalidOutputRoot(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -471,17 +404,11 @@ func testDisputeOutputRootBeyondProposedBlockInvalidOutputRoot(t *testing.T, all testDisputeRootBeyondProposedBlockInvalidOutputRoot(t, ctx, arena, &game.SplitGameHelper) } -func TestTestDisputeOutputRoot_ChangeClaimedOutputRoot_Standard(t *testing.T) { - testTestDisputeOutputRootChangeClaimedOutputRoot(t, config.AllocTypeStandard) -} - -func TestTestDisputeOutputRoot_ChangeClaimedOutputRoot_Multithreaded(t *testing.T) { - testTestDisputeOutputRootChangeClaimedOutputRoot(t, config.AllocTypeMTCannon) +func TestTestDisputeOutputRoot_ChangeClaimedOutputRoot(t *testing.T) { + RunTestAcrossVmTypes(t, testTestDisputeOutputRootChangeClaimedOutputRoot) } func testTestDisputeOutputRootChangeClaimedOutputRoot(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -493,22 +420,18 @@ func testTestDisputeOutputRootChangeClaimedOutputRoot(t *testing.T, allocType co testDisputeRootChangeClaimedRoot(t, ctx, arena, &game.SplitGameHelper) } -func TestInvalidateUnsafeProposal_Standard(t *testing.T) { - testInvalidateUnsafeProposal(t, config.AllocTypeStandard) -} - -func TestInvalidateUnsafeProposal_Multithreaded(t *testing.T) { - testInvalidateUnsafeProposal(t, config.AllocTypeMTCannon) -} - -func testInvalidateUnsafeProposal(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) +func TestInvalidateUnsafeProposal(t *testing.T) { ctx := context.Background() - tests := []struct { + type TestCase struct { name string strategy func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper - }{ + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("%v-%v", vm, test.name) + } + + tests := []TestCase{ { name: "Attack", strategy: func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { @@ -529,56 +452,47 @@ func testInvalidateUnsafeProposal(t *testing.T, allocType config.AllocType) { }, } - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - sys, l1Client := StartFaultDisputeSystem(t, WithSequencerWindowSize(100000), WithBatcherStopped(), WithAllocType(allocType)) - t.Cleanup(sys.Close) - - blockNum := uint64(1) - disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) - // Root claim is _dishonest_ because the required data is not available on L1 - game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", blockNum, disputegame.WithUnsafeProposal()) + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { + sys, l1Client := StartFaultDisputeSystem(t, WithSequencerWindowSize(100000), WithBatcherStopped(), WithAllocType(allocType)) + t.Cleanup(sys.Close) - correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice)) + blockNum := uint64(1) + disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) + // Root claim is _dishonest_ because the required data is not available on L1 + game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", blockNum, disputegame.WithUnsafeProposal()) - // Start the honest challenger - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) + correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice)) - game.DefendClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { - if parent.IsBottomGameRoot(ctx) { - return correctTrace.AttackClaim(ctx, parent) - } - return test.strategy(correctTrace, parent) - }) + // Start the honest challenger + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) - // Time travel past when the game will be resolvable. - sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, l1Client)) - - game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) - game.LogGameData(ctx) + game.DefendClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { + if parent.IsBottomGameRoot(ctx) { + return correctTrace.AttackClaim(ctx, parent) + } + return test.strategy(correctTrace, parent) }) - } -} -func TestInvalidateProposalForFutureBlock_Standard(t *testing.T) { - testInvalidateProposalForFutureBlock(t, config.AllocTypeStandard) -} + // Time travel past when the game will be resolvable. + sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, l1Client)) -func TestInvalidateProposalForFutureBlock_Multithreaded(t *testing.T) { - testInvalidateProposalForFutureBlock(t, config.AllocTypeMTCannon) + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) + game.LogGameData(ctx) + }, WithTestName(testName)) } -func testInvalidateProposalForFutureBlock(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) +func TestInvalidateProposalForFutureBlock(t *testing.T) { ctx := context.Background() - - tests := []struct { + type TestCase struct { name string strategy func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper - }{ + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("%v-%v", vm, test.name) + } + + tests := []TestCase{ { name: "Attack", strategy: func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { @@ -599,50 +513,41 @@ func testInvalidateProposalForFutureBlock(t *testing.T, allocType config.AllocTy }, } - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - sys, l1Client := StartFaultDisputeSystem(t, WithSequencerWindowSize(100000), WithAllocType(allocType)) - t.Cleanup(sys.Close) - - farFutureBlockNum := uint64(10_000_000) - disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) - // Root claim is _dishonest_ because the required data is not available on L1 - game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", farFutureBlockNum, common.Hash{0xaa}, disputegame.WithFutureProposal()) - - correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice)) + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { + sys, l1Client := StartFaultDisputeSystem(t, WithSequencerWindowSize(100000), WithAllocType(allocType)) + t.Cleanup(sys.Close) - // Start the honest challenger - game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) + farFutureBlockNum := uint64(10_000_000) + disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) + // Root claim is _dishonest_ because the required data is not available on L1 + game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", farFutureBlockNum, common.Hash{0xaa}, disputegame.WithFutureProposal()) - game.DefendClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { - if parent.IsBottomGameRoot(ctx) { - return correctTrace.AttackClaim(ctx, parent) - } - return test.strategy(correctTrace, parent) - }) + correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice)) - // Time travel past when the game will be resolvable. - sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, l1Client)) + // Start the honest challenger + game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) - game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) - game.LogGameData(ctx) + game.DefendClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { + if parent.IsBottomGameRoot(ctx) { + return correctTrace.AttackClaim(ctx, parent) + } + return test.strategy(correctTrace, parent) }) - } -} -func TestInvalidateCorrectProposalFutureBlock_Standard(t *testing.T) { - testInvalidateCorrectProposalFutureBlock(t, config.AllocTypeStandard) + // Time travel past when the game will be resolvable. + sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, l1Client)) + + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) + game.LogGameData(ctx) + }, WithTestName(testName)) } -func TestInvalidateCorrectProposalFutureBlock_Multithreaded(t *testing.T) { - testInvalidateCorrectProposalFutureBlock(t, config.AllocTypeMTCannon) +func TestInvalidateCorrectProposalFutureBlock(t *testing.T) { + RunTestAcrossVmTypes(t, testInvalidateCorrectProposalFutureBlock) } func testInvalidateCorrectProposalFutureBlock(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) ctx := context.Background() // Spin up the system without the batcher so the safe head doesn't advance sys, l1Client := StartFaultDisputeSystem(t, WithBatcherStopped(), WithSequencerWindowSize(100000), WithAllocType(allocType)) @@ -673,17 +578,11 @@ func testInvalidateCorrectProposalFutureBlock(t *testing.T, allocType config.All game.LogGameData(ctx) } -func TestOutputCannonHonestSafeTraceExtension_ValidRoot_Standard(t *testing.T) { - testOutputCannonHonestSafeTraceExtensionValidRoot(t, config.AllocTypeStandard) -} - -func TestOutputCannonHonestSafeTraceExtension_ValidRoot_Multithreaded(t *testing.T) { - testOutputCannonHonestSafeTraceExtensionValidRoot(t, config.AllocTypeMTCannon) +func TestOutputCannonHonestSafeTraceExtension_ValidRoot(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonHonestSafeTraceExtensionValidRoot) } func testOutputCannonHonestSafeTraceExtensionValidRoot(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, l1Client := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -733,17 +632,11 @@ func testOutputCannonHonestSafeTraceExtensionValidRoot(t *testing.T, allocType c game.WaitForGameStatus(ctx, gameTypes.GameStatusDefenderWon) } -func TestOutputCannonHonestSafeTraceExtension_InvalidRoot_Standard(t *testing.T) { - testOutputCannonHonestSafeTraceExtensionInvalidRoot(t, config.AllocTypeStandard) -} - -func TestOutputCannonHonestSafeTraceExtension_InvalidRoot_Multithreaded(t *testing.T) { - testOutputCannonHonestSafeTraceExtensionInvalidRoot(t, config.AllocTypeMTCannon) +func TestOutputCannonHonestSafeTraceExtension_InvalidRoot(t *testing.T) { + RunTestAcrossVmTypes(t, testOutputCannonHonestSafeTraceExtensionInvalidRoot) } func testOutputCannonHonestSafeTraceExtensionInvalidRoot(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, l1Client := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) @@ -780,17 +673,11 @@ func testOutputCannonHonestSafeTraceExtensionInvalidRoot(t *testing.T, allocType game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) } -func TestAgreeFirstBlockWithOriginOf1_Standard(t *testing.T) { - testAgreeFirstBlockWithOriginOf1(t, config.AllocTypeStandard) -} - -func TestAgreeFirstBlockWithOriginOf1_Multithreaded(t *testing.T) { - testAgreeFirstBlockWithOriginOf1(t, config.AllocTypeMTCannon) +func TestAgreeFirstBlockWithOriginOf1(t *testing.T) { + RunTestAcrossVmTypes(t, testAgreeFirstBlockWithOriginOf1) } func testAgreeFirstBlockWithOriginOf1(t *testing.T, allocType config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() sys, _ := StartFaultDisputeSystem(t, WithAllocType(allocType)) t.Cleanup(sys.Close) diff --git a/op-e2e/faultproofs/precompile_test.go b/op-e2e/faultproofs/precompile_test.go index d00b92705f804..53a0a0af4980b 100644 --- a/op-e2e/faultproofs/precompile_test.go +++ b/op-e2e/faultproofs/precompile_test.go @@ -4,13 +4,13 @@ import ( "bytes" "context" "encoding/json" + "fmt" "math" "math/big" "os/exec" "path/filepath" "testing" - op_e2e "github.com/ethereum-optimism/optimism/op-e2e" "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs" e2e_config "github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" @@ -33,190 +33,179 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testlog" ) -func TestPrecompiles_Standard(t *testing.T) { - testPrecompiles(t, e2e_config.AllocTypeStandard) -} - -func TestPrecompiles_Multithreaded(t *testing.T) { - testPrecompiles(t, e2e_config.AllocTypeMTCannon) -} - -func testPrecompiles(t *testing.T, allocType e2e_config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - for _, test := range proofs.PrecompileTestFixtures { - test := test - t.Run(test.Name, func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - genesisTime := hexutil.Uint64(0) - cfg := e2esys.IsthmusSystemConfig(t, &genesisTime, e2esys.WithAllocType(allocType)) - // We don't need a verifier - just the sequencer is enough - delete(cfg.Nodes, "verifier") - - sys, err := cfg.Start(t) - require.Nil(t, err, "Error starting up system") - - log := testlog.Logger(t, log.LevelInfo) - log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time) - - l1Client := sys.NodeClient("l1") - l2Seq := sys.NodeClient("sequencer") - rollupClient := sys.RollupClient("sequencer") - aliceKey := cfg.Secrets.Alice - - t.Log("Capture current L2 head as agreed starting point") - latestBlock, err := l2Seq.BlockByNumber(ctx, nil) - require.NoError(t, err) - agreedL2Output, err := rollupClient.OutputAtBlock(ctx, latestBlock.NumberU64()) - require.NoError(t, err, "could not retrieve l2 agreed block") - l2Head := agreedL2Output.BlockRef.Hash - l2OutputRoot := agreedL2Output.OutputRoot - - receipt := helpers.SendL2Tx(t, cfg, l2Seq, aliceKey, func(opts *helpers.TxOpts) { - opts.Gas = 1_000_000 - opts.ToAddr = &test.Address - opts.Nonce = 0 - opts.Data = test.Input - }) - - t.Log("Determine L2 claim") - l2ClaimBlockNumber := receipt.BlockNumber - l2Output, err := rollupClient.OutputAtBlock(ctx, l2ClaimBlockNumber.Uint64()) - require.NoError(t, err, "could not get expected output") - l2Claim := l2Output.OutputRoot - - t.Log("Determine L1 head that includes all batches required for L2 claim block") - require.NoError(t, wait.ForSafeBlock(ctx, rollupClient, l2ClaimBlockNumber.Uint64())) - l1HeadBlock, err := l1Client.BlockByNumber(ctx, nil) - require.NoError(t, err, "get l1 head block") - l1Head := l1HeadBlock.Hash() - - inputs := utils.LocalGameInputs{ - L1Head: l1Head, - L2Head: l2Head, - L2Claim: common.Hash(l2Claim), - L2OutputRoot: common.Hash(l2OutputRoot), - L2SequenceNumber: l2ClaimBlockNumber, - } - runCannon(t, ctx, sys, inputs) +func TestPrecompile(t *testing.T) { + tests := proofs.PrecompileTestFixtures + testName := func(vm string, test proofs.PrecompileTestFixture) string { + return fmt.Sprintf("%v-%v", test.Name, vm) + } + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType e2e_config.AllocType, test proofs.PrecompileTestFixture) { + ctx := context.Background() + genesisTime := hexutil.Uint64(0) + cfg := e2esys.IsthmusSystemConfig(t, &genesisTime, e2esys.WithAllocType(allocType)) + // We don't need a verifier - just the sequencer is enough + delete(cfg.Nodes, "verifier") + + sys, err := cfg.Start(t) + require.Nil(t, err, "Error starting up system") + + log := testlog.Logger(t, log.LevelInfo) + log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time) + + l1Client := sys.NodeClient("l1") + l2Seq := sys.NodeClient("sequencer") + rollupClient := sys.RollupClient("sequencer") + aliceKey := cfg.Secrets.Alice + + t.Log("Capture current L2 head as agreed starting point") + latestBlock, err := l2Seq.BlockByNumber(ctx, nil) + require.NoError(t, err) + agreedL2Output, err := rollupClient.OutputAtBlock(ctx, latestBlock.NumberU64()) + require.NoError(t, err, "could not retrieve l2 agreed block") + l2Head := agreedL2Output.BlockRef.Hash + l2OutputRoot := agreedL2Output.OutputRoot + + receipt := helpers.SendL2Tx(t, cfg, l2Seq, aliceKey, func(opts *helpers.TxOpts) { + opts.Gas = 1_000_000 + opts.ToAddr = &test.Address + opts.Nonce = 0 + opts.Data = test.Input }) - t.Run("DisputePrecompile-"+test.Name, func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - if !test.Accelerated { - t.Skipf("%v is not accelerated so no preimgae to upload", test.Name) - } - ctx := context.Background() - sys, _ := StartFaultDisputeSystem(t, WithLatestFork(), WithAllocType(allocType)) - - l2Seq := sys.NodeClient("sequencer") - aliceKey := sys.Cfg.Secrets.Alice - receipt := helpers.SendL2Tx(t, sys.Cfg, l2Seq, aliceKey, func(opts *helpers.TxOpts) { - opts.Gas = 1_000_000 - opts.ToAddr = &test.Address - opts.Nonce = 0 - opts.Data = test.Input - }) - - disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) - game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", receipt.BlockNumber.Uint64(), common.Hash{0x01, 0xaa}) - require.NotNil(t, game) - outputRootClaim := game.DisputeLastBlock(ctx) - game.LogGameData(ctx) - - honestChallenger := game.StartChallenger(ctx, "HonestActor", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) - - // Wait for the honest challenger to dispute the outputRootClaim. This creates a root of an execution game that we challenge by coercing - // a step at a preimage trace index. - outputRootClaim = outputRootClaim.WaitForCounterClaim(ctx) - - // Now the honest challenger is positioned as the defender of the execution game - // We then move to challenge it to induce a preimage load - preimageLoadCheck := game.CreateStepPreimageLoadCheck(ctx) - providerFunc := game.NewMemoizedCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) - game.ChallengeToPreimageLoad(ctx, providerFunc, utils.FirstPreimageLoadOfType("precompile"), preimageLoadCheck, false) - // The above method already verified the image was uploaded and step called successfully - // So we don't waste time resolving the game - that's tested elsewhere. - require.NoError(t, honestChallenger.Close()) - }) - } + t.Log("Determine L2 claim") + l2ClaimBlockNumber := receipt.BlockNumber + l2Output, err := rollupClient.OutputAtBlock(ctx, l2ClaimBlockNumber.Uint64()) + require.NoError(t, err, "could not get expected output") + l2Claim := l2Output.OutputRoot + + t.Log("Determine L1 head that includes all batches required for L2 claim block") + require.NoError(t, wait.ForSafeBlock(ctx, rollupClient, l2ClaimBlockNumber.Uint64())) + l1HeadBlock, err := l1Client.BlockByNumber(ctx, nil) + require.NoError(t, err, "get l1 head block") + l1Head := l1HeadBlock.Hash() + + inputs := utils.LocalGameInputs{ + L1Head: l1Head, + L2Head: l2Head, + L2Claim: common.Hash(l2Claim), + L2OutputRoot: common.Hash(l2OutputRoot), + L2SequenceNumber: l2ClaimBlockNumber, + } + runCannon(t, ctx, sys, inputs) + }, WithTestName(testName)) } -func TestGranitePrecompiles_Standard(t *testing.T) { - testGranitePrecompiles(t, e2e_config.AllocTypeStandard) -} +func TestDisputePrecompile(t *testing.T) { + tests := proofs.PrecompileTestFixtures + testName := func(vm string, test proofs.PrecompileTestFixture) string { + return fmt.Sprintf("%v-%v", test.Name, vm) + } + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType e2e_config.AllocType, test proofs.PrecompileTestFixture) { + if !test.Accelerated { + t.Skipf("%v is not accelerated so no preimgae to upload", test.Name) + } + ctx := context.Background() + sys, _ := StartFaultDisputeSystem(t, WithLatestFork(), WithAllocType(allocType)) + + l2Seq := sys.NodeClient("sequencer") + aliceKey := sys.Cfg.Secrets.Alice + receipt := helpers.SendL2Tx(t, sys.Cfg, l2Seq, aliceKey, func(opts *helpers.TxOpts) { + opts.Gas = 1_000_000 + opts.ToAddr = &test.Address + opts.Nonce = 0 + opts.Data = test.Input + }) -func TestGranitePrecompiles_Multithreaded(t *testing.T) { - testGranitePrecompiles(t, e2e_config.AllocTypeMTCannon) + disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) + game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", receipt.BlockNumber.Uint64(), common.Hash{0x01, 0xaa}) + require.NotNil(t, game) + outputRootClaim := game.DisputeLastBlock(ctx) + game.LogGameData(ctx) + + honestChallenger := game.StartChallenger(ctx, "HonestActor", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + + // Wait for the honest challenger to dispute the outputRootClaim. This creates a root of an execution game that we challenge by coercing + // a step at a preimage trace index. + outputRootClaim = outputRootClaim.WaitForCounterClaim(ctx) + + // Now the honest challenger is positioned as the defender of the execution game + // We then move to challenge it to induce a preimage load + preimageLoadCheck := game.CreateStepPreimageLoadCheck(ctx) + providerFunc := game.NewMemoizedCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.ChallengeToPreimageLoad(ctx, providerFunc, utils.FirstPreimageLoadOfType("precompile"), preimageLoadCheck, false) + // The above method already verified the image was uploaded and step called successfully + // So we don't waste time resolving the game - that's tested elsewhere. + require.NoError(t, honestChallenger.Close()) + }, WithTestName(testName)) } -func testGranitePrecompiles(t *testing.T, allocType e2e_config.AllocType) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - genesisTime := hexutil.Uint64(0) - cfg := e2esys.GraniteSystemConfig(t, &genesisTime, e2esys.WithAllocType(allocType)) - // We don't need a verifier - just the sequencer is enough - delete(cfg.Nodes, "verifier") - - sys, err := cfg.Start(t) - require.Nil(t, err, "Error starting up system") - - log := testlog.Logger(t, log.LevelInfo) - log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time) - - l1Client := sys.NodeClient("l1") - l2Seq := sys.NodeClient("sequencer") - rollupClient := sys.RollupClient("sequencer") - aliceKey := cfg.Secrets.Alice - - t.Log("Capture current L2 head as agreed starting point") - latestBlock, err := l2Seq.BlockByNumber(ctx, nil) - require.NoError(t, err) - agreedL2Output, err := rollupClient.OutputAtBlock(ctx, latestBlock.NumberU64()) - require.NoError(t, err, "could not retrieve l2 agreed block") - l2Head := agreedL2Output.BlockRef.Hash - l2OutputRoot := agreedL2Output.OutputRoot - - precompile := common.BytesToAddress([]byte{0x08}) - input := make([]byte, 113_000) - tx := types.MustSignNewTx(aliceKey, types.LatestSignerForChainID(cfg.L2ChainIDBig()), &types.DynamicFeeTx{ - ChainID: cfg.L2ChainIDBig(), - Nonce: 0, - GasTipCap: big.NewInt(1 * params.GWei), - GasFeeCap: big.NewInt(10 * params.GWei), - Gas: 25_000_000, - To: &precompile, - Value: big.NewInt(0), - Data: input, +func TestGranitePrecompiles(t *testing.T) { + RunTestAcrossVmTypes(t, func(t *testing.T, allocType e2e_config.AllocType) { + ctx := context.Background() + genesisTime := hexutil.Uint64(0) + cfg := e2esys.GraniteSystemConfig(t, &genesisTime, e2esys.WithAllocType(allocType)) + // We don't need a verifier - just the sequencer is enough + delete(cfg.Nodes, "verifier") + + sys, err := cfg.Start(t) + require.Nil(t, err, "Error starting up system") + + log := testlog.Logger(t, log.LevelInfo) + log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time) + + l1Client := sys.NodeClient("l1") + l2Seq := sys.NodeClient("sequencer") + rollupClient := sys.RollupClient("sequencer") + aliceKey := cfg.Secrets.Alice + + t.Log("Capture current L2 head as agreed starting point") + latestBlock, err := l2Seq.BlockByNumber(ctx, nil) + require.NoError(t, err) + agreedL2Output, err := rollupClient.OutputAtBlock(ctx, latestBlock.NumberU64()) + require.NoError(t, err, "could not retrieve l2 agreed block") + l2Head := agreedL2Output.BlockRef.Hash + l2OutputRoot := agreedL2Output.OutputRoot + + precompile := common.BytesToAddress([]byte{0x08}) + input := make([]byte, 113_000) + tx := types.MustSignNewTx(aliceKey, types.LatestSignerForChainID(cfg.L2ChainIDBig()), &types.DynamicFeeTx{ + ChainID: cfg.L2ChainIDBig(), + Nonce: 0, + GasTipCap: big.NewInt(1 * params.GWei), + GasFeeCap: big.NewInt(10 * params.GWei), + Gas: 25_000_000, + To: &precompile, + Value: big.NewInt(0), + Data: input, + }) + err = l2Seq.SendTransaction(ctx, tx) + require.NoError(t, err, "Should send bn256Pairing transaction") + // Expect a successful receipt to retrieve the EVM call trace so we can inspect the revert reason + receipt, err := wait.ForReceiptMaybe(ctx, l2Seq, tx.Hash(), types.ReceiptStatusSuccessful, false) + require.NotNil(t, err) + require.Contains(t, err.Error(), "bad elliptic curve pairing input size") + + t.Logf("Transaction hash %v", tx.Hash()) + t.Log("Determine L2 claim") + l2ClaimBlockNumber := receipt.BlockNumber + l2Output, err := rollupClient.OutputAtBlock(ctx, l2ClaimBlockNumber.Uint64()) + require.NoError(t, err, "could not get expected output") + l2Claim := l2Output.OutputRoot + + t.Log("Determine L1 head that includes all batches required for L2 claim block") + require.NoError(t, wait.ForSafeBlock(ctx, rollupClient, l2ClaimBlockNumber.Uint64())) + l1HeadBlock, err := l1Client.BlockByNumber(ctx, nil) + require.NoError(t, err, "get l1 head block") + l1Head := l1HeadBlock.Hash() + + inputs := utils.LocalGameInputs{ + L1Head: l1Head, + L2Head: l2Head, + L2Claim: common.Hash(l2Claim), + L2OutputRoot: common.Hash(l2OutputRoot), + L2SequenceNumber: l2ClaimBlockNumber, + } + runCannon(t, ctx, sys, inputs) }) - err = l2Seq.SendTransaction(ctx, tx) - require.NoError(t, err, "Should send bn256Pairing transaction") - // Expect a successful receipt to retrieve the EVM call trace so we can inspect the revert reason - receipt, err := wait.ForReceiptMaybe(ctx, l2Seq, tx.Hash(), types.ReceiptStatusSuccessful, false) - require.NotNil(t, err) - require.Contains(t, err.Error(), "bad elliptic curve pairing input size") - - t.Logf("Transaction hash %v", tx.Hash()) - t.Log("Determine L2 claim") - l2ClaimBlockNumber := receipt.BlockNumber - l2Output, err := rollupClient.OutputAtBlock(ctx, l2ClaimBlockNumber.Uint64()) - require.NoError(t, err, "could not get expected output") - l2Claim := l2Output.OutputRoot - - t.Log("Determine L1 head that includes all batches required for L2 claim block") - require.NoError(t, wait.ForSafeBlock(ctx, rollupClient, l2ClaimBlockNumber.Uint64())) - l1HeadBlock, err := l1Client.BlockByNumber(ctx, nil) - require.NoError(t, err, "get l1 head block") - l1Head := l1HeadBlock.Hash() - - inputs := utils.LocalGameInputs{ - L1Head: l1Head, - L2Head: l2Head, - L2Claim: common.Hash(l2Claim), - L2OutputRoot: common.Hash(l2OutputRoot), - L2SequenceNumber: l2ClaimBlockNumber, - } - runCannon(t, ctx, sys, inputs) } func runCannon(t *testing.T, ctx context.Context, sys *e2esys.System, inputs utils.LocalGameInputs, extraVmArgs ...string) { diff --git a/op-e2e/faultproofs/super_test.go b/op-e2e/faultproofs/super_test.go index bc45b07611b6c..801b75fa6beab 100644 --- a/op-e2e/faultproofs/super_test.go +++ b/op-e2e/faultproofs/super_test.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" - op_e2e "github.com/ethereum-optimism/optimism/op-e2e" "github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/batcher" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" @@ -27,77 +26,80 @@ import ( ) func TestCreateSuperCannonGame(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - sys.L2IDs() - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) - game.LogGameData(ctx) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + sys.L2IDs() + game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + game.LogGameData(ctx) + }, WithNextVMOnly[any]()) } func TestSuperCannonGame(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) - testCannonGame(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + testCannonGame(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + }, WithNextVMOnly[any]()) } func TestSuperCannonGame_ChallengeAllZeroClaim(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) - testCannonChallengeAllZeroClaim(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + testCannonChallengeAllZeroClaim(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + }, WithNextVMOnly[any]()) } func TestSuperCannonPublishCannonRootClaim(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - - tests := []struct { + type TestCase struct { disputeL2SequenceNumberOffset uint64 - }{ + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("Dispute_%v_%v", test.disputeL2SequenceNumberOffset, vm) + } + + tests := []TestCase{ {2}, {3}, {4}, {5}, {6}, } + vmStatusCh := make(chan byte, len(tests)) - for _, test := range tests { - test := test - t.Run(fmt.Sprintf("Dispute_%v", test.disputeL2SequenceNumberOffset), func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - b, err := sys.L1GethClient().BlockByNumber(ctx, nil) - require.NoError(t, err) - disputeL2SequenceNumber := b.Time() + test.disputeL2SequenceNumberOffset + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { + ctx := context.Background() - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, disputeL2SequenceNumber, common.Hash{0x01}) - game.DisputeLastBlock(ctx) - game.LogGameData(ctx) - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + b, err := sys.L1GethClient().BlockByNumber(ctx, nil) + require.NoError(t, err) + disputeL2SequenceNumber := b.Time() + test.disputeL2SequenceNumberOffset - splitDepth := game.SplitDepth(ctx) - t.Logf("Waiting for claim at depth %v (split depth %v)", splitDepth+1, splitDepth) - game.WaitForClaimAtDepth(ctx, splitDepth+1) + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, disputeL2SequenceNumber, common.Hash{0x01}) + game.DisputeLastBlock(ctx) + game.LogGameData(ctx) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - claims, err := game.Game.GetAllClaims(ctx, rpcblock.Latest) - require.NoError(t, err) - var bottomRootClaim types.Claim - for _, claim := range claims { - if claim.Depth() == splitDepth+1 { - require.True(t, bottomRootClaim == types.Claim{}, "Found multiple bottom root claims") - bottomRootClaim = claim - } + splitDepth := game.SplitDepth(ctx) + t.Logf("Waiting for claim at depth %v (split depth %v)", splitDepth+1, splitDepth) + game.WaitForClaimAtDepth(ctx, splitDepth+1) + + claims, err := game.Game.GetAllClaims(ctx, rpcblock.Latest) + require.NoError(t, err) + var bottomRootClaim types.Claim + for _, claim := range claims { + if claim.Depth() == splitDepth+1 { + require.True(t, bottomRootClaim == types.Claim{}, "Found multiple bottom root claims") + bottomRootClaim = claim } - require.True(t, bottomRootClaim != types.Claim{}, "Failed to find bottom root claim") - t.Logf("Bottom root claim: %v", bottomRootClaim.Value) - vmStatusCh <- bottomRootClaim.Value[0] - }) - } + } + require.True(t, bottomRootClaim != types.Claim{}, "Failed to find bottom root claim") + t.Logf("Bottom root claim: %v", bottomRootClaim.Value) + vmStatusCh <- bottomRootClaim.Value[0] + }, WithNextVMOnly[TestCase](), WithTestName[TestCase](testName)) // Cleanup ensures that the subtests run to completion before asserting the VM statuses t.Cleanup(func() { @@ -113,215 +115,224 @@ func TestSuperCannonPublishCannonRootClaim(t *testing.T) { } func TestSuperCannonDisputeGame(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - tests := []struct { + type TestCase struct { name string defendClaimDepth types.Depth - }{ + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("%v-%v", test.name, vm) + } + tests := []TestCase{ {"StepFirst", 0}, {"StepMiddle", 28}, {"StepInExtension", 1}, } - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01, 0xaa}) - game.LogGameData(ctx) - - disputeClaim := game.DisputeLastBlock(ctx) - splitDepth := game.SplitDepth(ctx) - - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - - game.SupportClaim( - ctx, - disputeClaim, - func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper { - if claim.Depth()+1 == splitDepth+test.defendClaimDepth { - return claim.Defend(ctx, common.Hash{byte(claim.Depth())}) - } else { - return claim.Attack(ctx, common.Hash{byte(claim.Depth())}) - } - }, - func(parentIdx int64) { - t.Log("Calling step on challenger's claim...") - honestActor := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { - c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) - }) - honestActor.StepFails(ctx, parentIdx, false) - honestActor.StepFails(ctx, parentIdx, true) - }, - ) - - sys.AdvanceL1Time(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) - - game.LogGameData(ctx) - game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) - }) - } -} - -func TestSuperCannonDefendStep(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) - testCannonDefendStep(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) -} - -func TestSuperCannonStepWithLargePreimage(t *testing.T) { - t.Skip("Skipping large preimage test due to cross-safe stall in the supervisor") - op_e2e.InitParallel(t, op_e2e.UsesCannon) - - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - - for _, id := range sys.L2IDs() { - require.NoError(t, sys.Batcher(id).Stop(ctx)) - } - // Manually send a tx from the correct batcher key to the batcher input with very large (invalid) data - // This forces op-program to load a large preimage. - for _, id := range sys.L2IDs() { - batcherKey := sys.L2OperatorKey(id, devkeys.BatcherRole) - batcherHelper := batcher.NewHelper(t, &batcherKey, sys.RollupConfig(id), sys.L1GethClient()) - t.Logf("Sending large invalid batch from batcher %v", id) - batcherHelper.SendLargeInvalidBatch(ctx) - } - for _, id := range sys.L2IDs() { - require.NoError(t, sys.Batcher(id).Start(ctx)) - } + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01, 0xaa}) + game.LogGameData(ctx) - L1Head, err := sys.L1GethClient().BlockByNumber(ctx, nil) - require.NoError(t, err) - t.Logf("L1 head %d (%x) timestamp: %v", L1Head.NumberU64(), L1Head.Hash(), L1Head.Time()) - l2Timestamp := L1Head.Time() - disputeGameFactory.WaitForSuperTimestamp(l2Timestamp, &disputegame.GameCfg{}) + disputeClaim := game.DisputeLastBlock(ctx) + splitDepth := game.SplitDepth(ctx) - // Dispute any block - it will have to read the L1 batches to see if the block is reached - game := disputeGameFactory.StartSuperCannonGameWithCorrectRootAtTimestamp(ctx, l2Timestamp) - topGameLeaf := game.DisputeBlock(ctx, l2Timestamp) - game.LogGameData(ctx) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) + game.SupportClaim( + ctx, + disputeClaim, + func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper { + if claim.Depth()+1 == splitDepth+test.defendClaimDepth { + return claim.Defend(ctx, common.Hash{byte(claim.Depth())}) + } else { + return claim.Attack(ctx, common.Hash{byte(claim.Depth())}) + } + }, + func(parentIdx int64) { + t.Log("Calling step on challenger's claim...") + honestActor := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { + c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) + }) + honestActor.StepFails(ctx, parentIdx, false) + honestActor.StepFails(ctx, parentIdx, true) + }, + ) - topGameLeaf = topGameLeaf.Attack(ctx, common.Hash{0x01}) + sys.AdvanceL1Time(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) - game.LogGameData(ctx) - // Now the honest challenger is positioned as the defender of the execution game. We then dispute it by inducing a large preimage load. - sender := crypto.PubkeyToAddress(aliceKey(t).PublicKey) - preimageLoadCheck := game.CreateStepLargePreimageLoadCheck(ctx, sender) - game.ChallengeToPreimageLoad(ctx, topGameLeaf, aliceKey(t), utils.PreimageLargerThan(preimage.MinPreimageSize), preimageLoadCheck, false) - // The above method already verified the image was uploaded and step called successfully - // So we don't waste time resolving the game - that's tested elsewhere. + game.LogGameData(ctx) + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) + }, WithNextVMOnly[TestCase](), WithTestName(testName)) } -func TestSuperCannonStepWithPreimage(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - testPreimageStep := func(t *testing.T, preimageType utils.PreimageOpt, preloadPreimage bool) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) +func TestSuperCannonDefendStep(t *testing.T) { + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + testCannonDefendStep(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + }, WithNextVMOnly[any]()) +} +func TestSuperCannonStepWithLargePreimage(t *testing.T) { + t.Skip("Skipping large preimage test due to cross-safe stall in the supervisor") + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) - status, err := sys.SupervisorClient().SyncStatus(ctx) + for _, id := range sys.L2IDs() { + require.NoError(t, sys.Batcher(id).Stop(ctx)) + } + // Manually send a tx from the correct batcher key to the batcher input with very large (invalid) data + // This forces op-program to load a large preimage. + for _, id := range sys.L2IDs() { + batcherKey := sys.L2OperatorKey(id, devkeys.BatcherRole) + batcherHelper := batcher.NewHelper(t, &batcherKey, sys.RollupConfig(id), sys.L1GethClient()) + t.Logf("Sending large invalid batch from batcher %v", id) + batcherHelper.SendLargeInvalidBatch(ctx) + } + for _, id := range sys.L2IDs() { + require.NoError(t, sys.Batcher(id).Start(ctx)) + } + + L1Head, err := sys.L1GethClient().BlockByNumber(ctx, nil) require.NoError(t, err) - l2Timestamp := status.SafeTimestamp + t.Logf("L1 head %d (%x) timestamp: %v", L1Head.NumberU64(), L1Head.Hash(), L1Head.Time()) + l2Timestamp := L1Head.Time() + disputeGameFactory.WaitForSuperTimestamp(l2Timestamp, &disputegame.GameCfg{}) + // Dispute any block - it will have to read the L1 batches to see if the block is reached game := disputeGameFactory.StartSuperCannonGameWithCorrectRootAtTimestamp(ctx, l2Timestamp) - topGameLeaf := game.DisputeLastBlock(ctx) + topGameLeaf := game.DisputeBlock(ctx, l2Timestamp) game.LogGameData(ctx) game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - // This attack creates a bottom game such that we will be making the last move at the bottom. (see game depth parameters for the super DG) - // This presents an opportunity for the challenger to step on our dishonest claim at the bottom. - // This assumes the execution game depth is even. But if it is odd, then this test should be set up more like the FDG counter part. topGameLeaf = topGameLeaf.Attack(ctx, common.Hash{0x01}) - // Now the honest challenger is positioned as the defender of the execution game. We then move to challenge it to induce a preimage load - preimageLoadCheck := game.CreateStepPreimageLoadCheck(ctx) - game.ChallengeToPreimageLoad(ctx, topGameLeaf, aliceKey(t), preimageType, preimageLoadCheck, preloadPreimage) + game.LogGameData(ctx) + // Now the honest challenger is positioned as the defender of the execution game. We then dispute it by inducing a large preimage load. + sender := crypto.PubkeyToAddress(aliceKey(t).PublicKey) + preimageLoadCheck := game.CreateStepLargePreimageLoadCheck(ctx, sender) + game.ChallengeToPreimageLoad(ctx, topGameLeaf, aliceKey(t), utils.PreimageLargerThan(preimage.MinPreimageSize), preimageLoadCheck, false) // The above method already verified the image was uploaded and step called successfully // So we don't waste time resolving the game - that's tested elsewhere. - } + }, WithNextVMOnly[any]()) +} +func TestSuperCannonStepWithPreimage_nonExistingPreimage(t *testing.T) { preimageConditions := []string{"keccak", "sha256", "blob"} - for _, preimageType := range preimageConditions { - preimageType := preimageType + testName := func(vm string, preimageType string) string { + return fmt.Sprintf("%v-%v", preimageType, vm) + } + + RunTestsAcrossVmTypes(t, preimageConditions, func(t *testing.T, allocType config.AllocType, preimageType string) { if preimageType == "blob" || preimageType == "sha256" { t.Skip("TODO(#15311): Add blob preimage test case. sha256 is also used for blobs") } - t.Run("non-existing preimage-"+preimageType, func(t *testing.T) { - testPreimageStep(t, utils.FirstPreimageLoadOfType(preimageType), false) - }) - } + testSuperPreimageStep(t, utils.FirstPreimageLoadOfType(preimageType), false, allocType) + }, WithNextVMOnly[string](), WithTestName(testName)) +} + +func TestSuperCannonStepWithPreimage_existingPreimage(t *testing.T) { // Only test pre-existing images with one type to save runtime - t.Run("preimage already exists", func(t *testing.T) { - testPreimageStep(t, utils.FirstKeccakPreimageLoad(), true) - }) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + testSuperPreimageStep(t, utils.FirstKeccakPreimageLoad(), true, allocType) + }, WithNextVMOnly[any](), WithTestNamePrefix[any]("preimage already exists")) } -func TestSuperCannonProposalValid_AttackWithCorrectTrace(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) +func testSuperPreimageStep(t *testing.T, preimageType utils.PreimageOpt, preloadPreimage bool, allocType config.AllocType) { ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGameWithCorrectRoot(ctx) - testCannonProposalValid_AttackWithCorrectTrace(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + + status, err := sys.SupervisorClient().SyncStatus(ctx) + require.NoError(t, err) + l2Timestamp := status.SafeTimestamp + + game := disputeGameFactory.StartSuperCannonGameWithCorrectRootAtTimestamp(ctx, l2Timestamp) + topGameLeaf := game.DisputeLastBlock(ctx) + game.LogGameData(ctx) + + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) + + // This attack creates a bottom game such that we will be making the last move at the bottom. (see game depth parameters for the super DG) + // This presents an opportunity for the challenger to step on our dishonest claim at the bottom. + // This assumes the execution game depth is even. But if it is odd, then this test should be set up more like the FDG counter part. + topGameLeaf = topGameLeaf.Attack(ctx, common.Hash{0x01}) + + // Now the honest challenger is positioned as the defender of the execution game. We then move to challenge it to induce a preimage load + preimageLoadCheck := game.CreateStepPreimageLoadCheck(ctx) + game.ChallengeToPreimageLoad(ctx, topGameLeaf, aliceKey(t), preimageType, preimageLoadCheck, preloadPreimage) + // The above method already verified the image was uploaded and step called successfully + // So we don't waste time resolving the game - that's tested elsewhere. +} + +func TestSuperCannonProposalValid_AttackWithCorrectTrace(t *testing.T) { + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGameWithCorrectRoot(ctx) + testCannonProposalValid_AttackWithCorrectTrace(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + }, WithNextVMOnly[any]()) } func TestSuperCannonProposalValid_DefendWithCorrectTrace(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGameWithCorrectRoot(ctx) - testCannonProposalValid_DefendWithCorrectTrace(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGameWithCorrectRoot(ctx) + testCannonProposalValid_DefendWithCorrectTrace(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + }, WithNextVMOnly[any]()) } func TestSuperCannonPoisonedPostState(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) - testCannonPoisonedPostState(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + testCannonPoisonedPostState(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + }, WithNextVMOnly[any]()) } func TestSuperCannonRootBeyondProposedBlock_ValidRoot(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGameWithCorrectRoot(ctx) - testDisputeRootBeyondProposedBlockValidOutputRoot(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGameWithCorrectRoot(ctx) + testDisputeRootBeyondProposedBlockValidOutputRoot(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + }, WithNextVMOnly[any]()) } func TestSuperCannonRootBeyondProposedBlock_InvalidRoot(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) - testDisputeRootBeyondProposedBlockInvalidOutputRoot(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + testDisputeRootBeyondProposedBlockInvalidOutputRoot(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + }, WithNextVMOnly[any]()) } func TestSuperCannonRootChangeClaimedRoot(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) - testDisputeRootChangeClaimedRoot(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + testDisputeRootChangeClaimedRoot(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) + }, WithNextVMOnly[any]()) } func TestSuperInvalidateUnsafeProposal(t *testing.T) { t.Skip("TODO(#15321): Challenger does not respond to unsafe proposals") - op_e2e.InitParallel(t, op_e2e.UsesCannon) ctx := context.Background() - tests := []struct { + type TestCase struct { name string strategy func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper - }{ + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("%v-%v", test.name, vm) + } + tests := []TestCase{ { name: "Attack", strategy: func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { @@ -342,74 +353,71 @@ func TestSuperInvalidateUnsafeProposal(t *testing.T) { }, } - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - - client := sys.SupervisorClient() - status, err := client.SyncStatus(ctx) - require.NoError(t, err, "Failed to get sync status") - // Ensure that the superchain has progressed a bit past the genesis timestamp - disputeGameFactory.WaitForSuperTimestamp(status.SafeTimestamp+4, &disputegame.GameCfg{}) - // halt the safe chain - for _, id := range sys.L2IDs() { - require.NoError(t, sys.Batcher(id).Stop(ctx)) - } + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + + client := sys.SupervisorClient() + status, err := client.SyncStatus(ctx) + require.NoError(t, err, "Failed to get sync status") + // Ensure that the superchain has progressed a bit past the genesis timestamp + disputeGameFactory.WaitForSuperTimestamp(status.SafeTimestamp+4, &disputegame.GameCfg{}) + // halt the safe chain + for _, id := range sys.L2IDs() { + require.NoError(t, sys.Batcher(id).Stop(ctx)) + } - status, err = client.SyncStatus(ctx) - require.NoError(t, err, "Failed to get sync status") + status, err = client.SyncStatus(ctx) + require.NoError(t, err, "Failed to get sync status") - // Wait for any client to advance its unsafe head past the safe chain. We know this head will remain unsafe since the batc - l2Client := sys.L2GethClient(sys.L2IDs()[0], "sequencer") - require.NoError(t, wait.ForNextBlock(ctx, l2Client)) - head, err := l2Client.BlockByNumber(ctx, nil) - require.NoError(t, err, "Failed to get head block") - unsafeTimestamp := head.Time() + // Wait for any client to advance its unsafe head past the safe chain. We know this head will remain unsafe since the batc + l2Client := sys.L2GethClient(sys.L2IDs()[0], "sequencer") + require.NoError(t, wait.ForNextBlock(ctx, l2Client)) + head, err := l2Client.BlockByNumber(ctx, nil) + require.NoError(t, err, "Failed to get head block") + unsafeTimestamp := head.Time() - // Root claim is _dishonest_ because the required data is not available on L1 - unsafeSuper := createSuperRoot(t, ctx, sys, unsafeTimestamp) - unsafeRoot := eth.SuperRoot(unsafeSuper) - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, unsafeTimestamp, common.Hash(unsafeRoot), disputegame.WithFutureProposal()) + // Root claim is _dishonest_ because the required data is not available on L1 + unsafeSuper := createSuperRoot(t, ctx, sys, unsafeTimestamp) + unsafeRoot := eth.SuperRoot(unsafeSuper) + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, unsafeTimestamp, common.Hash(unsafeRoot), disputegame.WithFutureProposal()) - correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { - c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) - }) - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) + correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { + c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) + }) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - game.SupportClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { - if parent.IsBottomGameRoot(ctx) { - return correctTrace.AttackClaim(ctx, parent) - } - return test.strategy(correctTrace, parent) + game.SupportClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { + if parent.IsBottomGameRoot(ctx) { + return correctTrace.AttackClaim(ctx, parent) + } + return test.strategy(correctTrace, parent) + }, + func(parentIdx int64) { + t.Log("Calling step on challenger's claim...") + correctTrace.StepFails(ctx, parentIdx, false) + correctTrace.StepFails(ctx, parentIdx, true) }, - func(parentIdx int64) { - t.Log("Calling step on challenger's claim...") - correctTrace.StepFails(ctx, parentIdx, false) - correctTrace.StepFails(ctx, parentIdx, true) - }, - ) - - // Time travel past when the game will be resolvable. - sys.AdvanceL1Time(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) - - game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) - game.LogGameData(ctx) - }) - } + ) + + // Time travel past when the game will be resolvable. + sys.AdvanceL1Time(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) + + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) + game.LogGameData(ctx) + }, WithNextVMOnly[TestCase](), WithTestName(testName)) } func TestSuperInvalidateProposalForFutureBlock(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) ctx := context.Background() - - tests := []struct { + type TestCase struct { name string strategy func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper - }{ + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("%v-%v", test.name, vm) + } + tests := []TestCase{ { name: "Attack", strategy: func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { @@ -430,50 +438,48 @@ func TestSuperInvalidateProposalForFutureBlock(t *testing.T) { }, } - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - // Root claim is _dishonest_ because the required data is not available on L1 - farFutureTimestamp := time.Now().Add(time.Second * 10_000_000).Unix() - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, uint64(farFutureTimestamp), common.Hash{0x01}, disputegame.WithFutureProposal()) - correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { - c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) - }) - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + // Root claim is _dishonest_ because the required data is not available on L1 + farFutureTimestamp := time.Now().Add(time.Second * 10_000_000).Unix() + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, uint64(farFutureTimestamp), common.Hash{0x01}, disputegame.WithFutureProposal()) + correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { + c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) + }) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - game.SupportClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { - if parent.IsBottomGameRoot(ctx) { - return correctTrace.AttackClaim(ctx, parent) - } - return test.strategy(correctTrace, parent) + game.SupportClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { + if parent.IsBottomGameRoot(ctx) { + return correctTrace.AttackClaim(ctx, parent) + } + return test.strategy(correctTrace, parent) + }, + func(parentIdx int64) { + t.Log("Calling step on challenger's claim...") + correctTrace.StepFails(ctx, parentIdx, false) + correctTrace.StepFails(ctx, parentIdx, true) }, - func(parentIdx int64) { - t.Log("Calling step on challenger's claim...") - correctTrace.StepFails(ctx, parentIdx, false) - correctTrace.StepFails(ctx, parentIdx, true) - }, - ) - - // Time travel past when the game will be resolvable. - sys.AdvanceL1Time(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) - - game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) - game.LogGameData(ctx) - }) - } + ) + + // Time travel past when the game will be resolvable. + sys.AdvanceL1Time(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) + + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) + game.LogGameData(ctx) + }, WithNextVMOnly[TestCase](), WithTestName(testName)) } func TestSuperInvalidateCorrectProposalFutureBlock(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) ctx := context.Background() - - tests := []struct { + type TestCase struct { name string strategy func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper - }{ + } + testName := func(vm string, test TestCase) string { + return fmt.Sprintf("%v-%v", test.name, vm) + } + tests := []TestCase{ { name: "Attack", strategy: func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { @@ -494,200 +500,199 @@ func TestSuperInvalidateCorrectProposalFutureBlock(t *testing.T) { }, } - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - client := sys.SupervisorClient() - - status, err := client.SyncStatus(ctx) - require.NoError(t, err, "Failed to get sync status") - superRoot, err := client.SuperRootAtTimestamp(ctx, hexutil.Uint64(status.SafeTimestamp)) - require.NoError(t, err, "Failed to get super root at safe timestamp") - - // Stop the batcher so the safe head doesn't advance - for _, id := range sys.L2IDs() { - require.NoError(t, sys.Batcher(id).Stop(ctx)) - } + RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + client := sys.SupervisorClient() + + status, err := client.SyncStatus(ctx) + require.NoError(t, err, "Failed to get sync status") + superRoot, err := client.SuperRootAtTimestamp(ctx, hexutil.Uint64(status.SafeTimestamp)) + require.NoError(t, err, "Failed to get super root at safe timestamp") - // Create a dispute game with a proposal that is valid at `superRoot.Timestamp`, but that claims to correspond to timestamp - // `superRoot.Timestamp + 100000`. This is dishonest, because the superchain hasn't reached this timestamp yet. - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, superRoot.Timestamp+100_000, common.Hash(superRoot.SuperRoot), disputegame.WithFutureProposal()) + // Stop the batcher so the safe head doesn't advance + for _, id := range sys.L2IDs() { + require.NoError(t, sys.Batcher(id).Stop(ctx)) + } - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) + // Create a dispute game with a proposal that is valid at `superRoot.Timestamp`, but that claims to correspond to timestamp + // `superRoot.Timestamp + 100000`. This is dishonest, because the superchain hasn't reached this timestamp yet. + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, superRoot.Timestamp+100_000, common.Hash(superRoot.SuperRoot), disputegame.WithFutureProposal()) - correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { - c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) - }) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - game.SupportClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { - if parent.IsBottomGameRoot(ctx) { - return correctTrace.AttackClaim(ctx, parent) - } - return test.strategy(correctTrace, parent) - }, - func(parentIdx int64) { - t.Log("Calling step on challenger's claim...") - correctTrace.StepFails(ctx, parentIdx, false) - correctTrace.StepFails(ctx, parentIdx, true) - }, - ) - game.LogGameData(ctx) - - // Time travel past when the game will be resolvable. - sys.AdvanceL1Time(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) - - game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) - game.LogGameData(ctx) + correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { + c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) }) - } + + game.SupportClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { + if parent.IsBottomGameRoot(ctx) { + return correctTrace.AttackClaim(ctx, parent) + } + return test.strategy(correctTrace, parent) + }, + func(parentIdx int64) { + t.Log("Calling step on challenger's claim...") + correctTrace.StepFails(ctx, parentIdx, false) + correctTrace.StepFails(ctx, parentIdx, true) + }, + ) + game.LogGameData(ctx) + + // Time travel past when the game will be resolvable. + sys.AdvanceL1Time(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) + + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) + game.LogGameData(ctx) + }, WithNextVMOnly[TestCase](), WithTestName(testName)) } func TestSuperCannonHonestSafeTraceExtensionValidRoot(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - client := sys.SupervisorClient() - // Wait for there to be there are safe L2 blocks past the claimed safe timestamp that have data available on L1 within - // the commitment stored in the dispute game. - status, err := client.SyncStatus(ctx) - require.NoError(t, err, "Failed to get sync status") - safeTimestamp := status.SafeTimestamp - - disputeGameFactory.WaitForSuperTimestamp(safeTimestamp, new(disputegame.GameCfg)) - - game := disputeGameFactory.StartSuperCannonGameWithCorrectRootAtTimestamp(ctx, safeTimestamp-1) - require.NotNil(t, game) - - // Create a correct trace actor with an honest trace extending to L2 block #5 - // Notably, L2 block #5 is a valid block within the safe chain, and the data required to reproduce it - // will be committed to within the L1 head of the dispute game. - correctTracePlus1 := game.CreateHonestActor(ctx, - disputegame.WithPrivKey(malloryKey(t)), - disputegame.WithClaimedL2BlockNumber(safeTimestamp), - func(c *disputegame.HonestActorConfig) { - c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) - }, - ) + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + client := sys.SupervisorClient() + // Wait for there to be there are safe L2 blocks past the claimed safe timestamp that have data available on L1 within + // the commitment stored in the dispute game. + status, err := client.SyncStatus(ctx) + require.NoError(t, err, "Failed to get sync status") + safeTimestamp := status.SafeTimestamp + + disputeGameFactory.WaitForSuperTimestamp(safeTimestamp, new(disputegame.GameCfg)) + + game := disputeGameFactory.StartSuperCannonGameWithCorrectRootAtTimestamp(ctx, safeTimestamp-1) + require.NotNil(t, game) + + // Create a correct trace actor with an honest trace extending to L2 block #5 + // Notably, L2 block #5 is a valid block within the safe chain, and the data required to reproduce it + // will be committed to within the L1 head of the dispute game. + correctTracePlus1 := game.CreateHonestActor(ctx, + disputegame.WithPrivKey(malloryKey(t)), + disputegame.WithClaimedL2BlockNumber(safeTimestamp), + func(c *disputegame.HonestActorConfig) { + c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) + }, + ) - // Start the honest challenger. They will defend the root claim. - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) + // Start the honest challenger. They will defend the root claim. + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - root := game.RootClaim(ctx) - // Have to disagree with the root claim - we're trying to invalidate a valid output root - firstAttack := root.Attack(ctx, common.Hash{0xdd}) - game.SupportClaim(ctx, firstAttack, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { - return correctTracePlus1.CounterClaim(ctx, parent) - }, func(parentClaimIdx int64) { - t.Logf("Waiting for bottom claim %v to be countered", parentClaimIdx) - claim, err := game.Game.GetClaim(ctx, uint64(parentClaimIdx)) - require.NoError(t, err) - require.Equal(t, game.MaxDepth(ctx), claim.Position.Depth()) - game.WaitForCountered(ctx, parentClaimIdx) - }) - game.LogGameData(ctx) + root := game.RootClaim(ctx) + // Have to disagree with the root claim - we're trying to invalidate a valid output root + firstAttack := root.Attack(ctx, common.Hash{0xdd}) + game.SupportClaim(ctx, firstAttack, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { + return correctTracePlus1.CounterClaim(ctx, parent) + }, func(parentClaimIdx int64) { + t.Logf("Waiting for bottom claim %v to be countered", parentClaimIdx) + claim, err := game.Game.GetClaim(ctx, uint64(parentClaimIdx)) + require.NoError(t, err) + require.Equal(t, game.MaxDepth(ctx), claim.Position.Depth()) + game.WaitForCountered(ctx, parentClaimIdx) + }) + game.LogGameData(ctx) - // Time travel past when the game will be resolvable. - sys.AdvanceL1Time(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) + // Time travel past when the game will be resolvable. + sys.AdvanceL1Time(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) - game.WaitForGameStatus(ctx, gameTypes.GameStatusDefenderWon) + game.WaitForGameStatus(ctx, gameTypes.GameStatusDefenderWon) + }, WithNextVMOnly[any]()) } func TestSuperCannonHonestSafeTraceExtensionInvalidRoot(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - client := sys.SupervisorClient() - // Wait for there to be there are safe L2 blocks past the claimed safe timestamp that have data available on L1 within - // the commitment stored in the dispute game. - status, err := client.SyncStatus(ctx) - require.NoError(t, err, "Failed to get sync status") - safeTimestamp := status.SafeTimestamp + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() - disputeGameFactory.WaitForSuperTimestamp(safeTimestamp, new(disputegame.GameCfg)) + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + client := sys.SupervisorClient() + // Wait for there to be there are safe L2 blocks past the claimed safe timestamp that have data available on L1 within + // the commitment stored in the dispute game. + status, err := client.SyncStatus(ctx) + require.NoError(t, err, "Failed to get sync status") + safeTimestamp := status.SafeTimestamp - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, safeTimestamp-1, common.Hash{0xCA, 0xFE}) - require.NotNil(t, game) + disputeGameFactory.WaitForSuperTimestamp(safeTimestamp, new(disputegame.GameCfg)) - // Create a correct trace actor with an honest trace extending to safeTimestamp - correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { - c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) - }) + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, safeTimestamp-1, common.Hash{0xCA, 0xFE}) + require.NotNil(t, game) - // Create a correct trace actor with an honest trace extending to L2 block #5 - // Notably, L2 block #5 is a valid block within the safe chain, and the data required to reproduce it - // will be committed to within the L1 head of the dispute game. - correctTracePlus1 := game.CreateHonestActor(ctx, - disputegame.WithPrivKey(malloryKey(t)), - disputegame.WithClaimedL2BlockNumber(safeTimestamp), - func(c *disputegame.HonestActorConfig) { + // Create a correct trace actor with an honest trace extending to safeTimestamp + correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) - }, - ) + }) - // Start the honest challenger. They will defend the root claim. - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) + // Create a correct trace actor with an honest trace extending to L2 block #5 + // Notably, L2 block #5 is a valid block within the safe chain, and the data required to reproduce it + // will be committed to within the L1 head of the dispute game. + correctTracePlus1 := game.CreateHonestActor(ctx, + disputegame.WithPrivKey(malloryKey(t)), + disputegame.WithClaimedL2BlockNumber(safeTimestamp), + func(c *disputegame.HonestActorConfig) { + c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) + }, + ) - claim := game.RootClaim(ctx) - game.SupportClaim(ctx, claim, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { - // Have to disagree with the root claim - we're trying to invalidate a valid output root - if parent.IsRootClaim() { - return parent.Attack(ctx, common.Hash{0xdd}) - } - return correctTracePlus1.CounterClaim(ctx, parent) - }, func(parentClaimIdx int64) { - correctTrace.StepFails(ctx, parentClaimIdx, true) - correctTrace.StepFails(ctx, parentClaimIdx, false) - correctTracePlus1.StepFails(ctx, parentClaimIdx, true) - correctTracePlus1.StepFails(ctx, parentClaimIdx, false) - }) - game.LogGameData(ctx) + // Start the honest challenger. They will defend the root claim. + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - // Time travel past when the game will be resolvable. - sys.AdvanceL1Time(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) + claim := game.RootClaim(ctx) + game.SupportClaim(ctx, claim, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { + // Have to disagree with the root claim - we're trying to invalidate a valid output root + if parent.IsRootClaim() { + return parent.Attack(ctx, common.Hash{0xdd}) + } + return correctTracePlus1.CounterClaim(ctx, parent) + }, func(parentClaimIdx int64) { + correctTrace.StepFails(ctx, parentClaimIdx, true) + correctTrace.StepFails(ctx, parentClaimIdx, false) + correctTracePlus1.StepFails(ctx, parentClaimIdx, true) + correctTracePlus1.StepFails(ctx, parentClaimIdx, false) + }) + game.LogGameData(ctx) + + // Time travel past when the game will be resolvable. + sys.AdvanceL1Time(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) - game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) + }, WithNextVMOnly[any]()) } func TestSuperCannonGame_HonestCallsSteps(t *testing.T) { - op_e2e.InitParallel(t, op_e2e.UsesCannon) - ctx := context.Background() - sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) - game := disputeGameFactory.StartSuperCannonGameWithCorrectRoot(ctx) - game.LogGameData(ctx) + RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) + game := disputeGameFactory.StartSuperCannonGameWithCorrectRoot(ctx) + game.LogGameData(ctx) - correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { - c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) - }) - game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) + correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { + c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) + }) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) - rootAttack := correctTrace.AttackClaim(ctx, game.RootClaim(ctx)) - game.DefendClaim(ctx, rootAttack, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { - switch { - case parent.IsOutputRoot(ctx): - parent.RequireCorrectOutputRoot(ctx) - if parent.IsOutputRootLeaf(ctx) { - return parent.Attack(ctx, common.Hash{0x01, 0xaa}) - } else { + rootAttack := correctTrace.AttackClaim(ctx, game.RootClaim(ctx)) + game.DefendClaim(ctx, rootAttack, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { + switch { + case parent.IsOutputRoot(ctx): + parent.RequireCorrectOutputRoot(ctx) + if parent.IsOutputRootLeaf(ctx) { + return parent.Attack(ctx, common.Hash{0x01, 0xaa}) + } else { + return correctTrace.DefendClaim(ctx, parent) + } + case parent.IsBottomGameRoot(ctx): + return correctTrace.AttackClaim(ctx, parent) + default: return correctTrace.DefendClaim(ctx, parent) } - case parent.IsBottomGameRoot(ctx): - return correctTrace.AttackClaim(ctx, parent) - default: - return correctTrace.DefendClaim(ctx, parent) - } - }) - game.LogGameData(ctx) + }) + game.LogGameData(ctx) - sys.AdvanceL1Time(game.MaxClockDuration(ctx)) - require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) - game.WaitForGameStatus(ctx, gameTypes.GameStatusDefenderWon) + sys.AdvanceL1Time(game.MaxClockDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, sys.L1GethClient())) + game.WaitForGameStatus(ctx, gameTypes.GameStatusDefenderWon) + }, WithNextVMOnly[any]()) } func createSuperRoot(t *testing.T, ctx context.Context, sys interop.SuperSystem, timestamp uint64) *eth.SuperV1 { diff --git a/op-e2e/faultproofs/util.go b/op-e2e/faultproofs/util.go index 0140d4f77e01f..518a7579c46b8 100644 --- a/op-e2e/faultproofs/util.go +++ b/op-e2e/faultproofs/util.go @@ -2,8 +2,10 @@ package faultproofs import ( "crypto/ecdsa" + "fmt" "testing" + op_e2e "github.com/ethereum-optimism/optimism/op-e2e" "github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" @@ -116,3 +118,73 @@ func SendKZGPointEvaluationTx(t *testing.T, sys *e2esys.System, l2Node string, p opts.Data = common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a") }) } + +type VMTest func(t *testing.T, allocType config.AllocType) + +type vmTestOptions[T any] struct { + testNameModifier func(vmName string, testCase T) string + allocTypes []config.AllocType +} + +func defaultVmTestOptions[T any]() vmTestOptions[T] { + return vmTestOptions[T]{ + testNameModifier: func(vmName string, testcase T) string { + return vmName + }, + allocTypes: []config.AllocType{ + config.AllocTypeMTCannon, + config.AllocTypeMTCannonNext, + }, + } +} + +type VMTestOption[T any] func(*vmTestOptions[T]) + +func WithNextVMOnly[T any]() VMTestOption[T] { + return func(o *vmTestOptions[T]) { + o.allocTypes = []config.AllocType{config.AllocTypeMTCannonNext} + } +} + +func WithTestNamePrefix[T any](prefix string) VMTestOption[T] { + return func(o *vmTestOptions[T]) { + o.testNameModifier = func(vmName string, _ T) string { + return fmt.Sprintf("%v-%v", prefix, vmName) + } + } +} + +func WithTestName[T any](testName func(vmName string, _ T) string) VMTestOption[T] { + return func(o *vmTestOptions[T]) { + o.testNameModifier = testName + } +} + +// RunTestAcrossVmTypes Runs a single test case across multiple vm types +func RunTestAcrossVmTypes(t *testing.T, test VMTest, opts ...VMTestOption[any]) { + vmTestCase := func(t *testing.T, allocType config.AllocType, _ any) { + test(t, allocType) + } + RunTestsAcrossVmTypes[any](t, []any{nil}, vmTestCase, opts...) +} + +type VMTestCase[T any] func(t *testing.T, allocType config.AllocType, testcase T) + +// RunTestsAcrossVmTypes Runs a set of testCases, each testCase is run across multiple vm types +func RunTestsAcrossVmTypes[T any](t *testing.T, testCases []T, test VMTestCase[T], opts ...VMTestOption[T]) { + op_e2e.InitParallel(t, op_e2e.UsesCannon) + options := defaultVmTestOptions[T]() + for _, opt := range opts { + opt(&options) + } + + for _, testCase := range testCases { + for _, allocType := range options.allocTypes { + testName := options.testNameModifier(string(allocType), testCase) + t.Run(testName, func(t *testing.T) { + op_e2e.InitParallel(t, op_e2e.UsesCannon) + test(t, allocType, testCase) + }) + } + } +} diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index dcc4429b032cc..61bc546753c00 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -389,6 +389,8 @@ func (sys *System) PrestateVariant() challenger.PrestateVariant { switch sys.AllocType() { case config.AllocTypeMTCannon: return challenger.MTCannonVariant + case config.AllocTypeMTCannonNext: + return challenger.MTCannonNextVariant default: return challenger.STCannonVariant } diff --git a/op-preimage/README.md b/op-preimage/README.md index 82a3cf2471966..26b146049d8c7 100644 --- a/op-preimage/README.md +++ b/op-preimage/README.md @@ -4,5 +4,5 @@ Read more about the Preimage Oracle in the [specs](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/index.md#pre-image-oracle). -See [op-program](../op-program) and [Cannon client examples](../cannon/testdata/example) for client-side usage. +See [op-program](../op-program) and [Cannon client examples](../cannon/testdata) for client-side usage. See [Cannon `mipsevm`](../cannon/mipsevm) for server-side usage. diff --git a/op-program/Dockerfile.repro b/op-program/Dockerfile.repro index cda354c3e3ecc..f06d86548e2f5 100644 --- a/op-program/Dockerfile.repro +++ b/op-program/Dockerfile.repro @@ -38,7 +38,7 @@ RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache GIT_DATE="$GIT_DATE" \ CANNON_VERSION="$CANNON_VERSION" \ OP_PROGRAM_VERSION="$OP_PROGRAM_VERSION" \ - build-all + build-current # Exports files to the specified output location. # Writing files to host requires buildkit to be enabled. diff --git a/op-program/Dockerfile.repro.next b/op-program/Dockerfile.repro.next new file mode 100644 index 0000000000000..b76ec4fdccfc8 --- /dev/null +++ b/op-program/Dockerfile.repro.next @@ -0,0 +1,49 @@ +FROM golang:1.23.8-alpine3.21 AS src + +RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash just + +COPY ./go.mod /app/go.mod +COPY ./go.sum /app/go.sum + +WORKDIR /app + +RUN echo "go mod cache: $(go env GOMODCACHE)" +RUN echo "go build cache: $(go env GOCACHE)" + +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build go mod download + +COPY . /app + +# we need a separate stage for src so we can build a service provides prestates for unnanounced chains +FROM src AS builder +# We avoid copying the full .git dir into the build for just some metadata. +# Instead, specify: +# --build-arg GIT_COMMIT=$(git rev-parse HEAD) +# --build-arg GIT_DATE=$(git show -s --format='%ct') +ARG GIT_COMMIT +ARG GIT_DATE + +ARG CANNON_VERSION=v0.0.0 +ARG OP_PROGRAM_VERSION=v0.0.0 + +ARG TARGETOS TARGETARCH + +WORKDIR /app +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build just \ + -d /app/op-program \ + -f /app/op-program/repro.justfile \ + GOOS="$TARGETOS" \ + GOARCH="$TARGETARCH" \ + GIT_COMMIT="$GIT_COMMIT" \ + GIT_DATE="$GIT_DATE" \ + CANNON_VERSION="$CANNON_VERSION" \ + OP_PROGRAM_VERSION="$OP_PROGRAM_VERSION" \ + build-next + +# Exports files to the specified output location. +# Writing files to host requires buildkit to be enabled. +# e.g. `BUILDKIT=1 docker build ...` +FROM scratch AS export-stage +COPY --from=builder /app/op-program/bin/meta-mt64Next.json . +COPY --from=builder /app/op-program/bin/prestate-mt64Next.bin.gz . +COPY --from=builder /app/op-program/bin/prestate-proof-mt64Next.json . diff --git a/op-program/Dockerfile.repro.next.dockerignore b/op-program/Dockerfile.repro.next.dockerignore new file mode 100644 index 0000000000000..1a9566cebcc43 --- /dev/null +++ b/op-program/Dockerfile.repro.next.dockerignore @@ -0,0 +1,16 @@ +# exclude everything by default to limit context size and cache miss +* +# module definition +!go.* +# internal dependencies +!cannon/ +!op-alt-da/ +!op-node/ +!op-preimage/ +!op-program/ +!op-service/ +!op-supervisor/ + +**/bin +**/testdata +**/tests \ No newline at end of file diff --git a/op-program/Makefile b/op-program/Makefile index 70982f079ac64..6edf2eb1d0e74 100644 --- a/op-program/Makefile +++ b/op-program/Makefile @@ -51,13 +51,19 @@ check-custom-chains: op-program-host ./bin/op-program configs check-custom-chains reproducible-prestate: check-custom-chains + @docker build --output ./bin/ --progress plain -f Dockerfile.repro.next ../ @docker build --output ./bin/ --progress plain -f Dockerfile.repro ../ + @echo "-------------------- Production Prestates --------------------\n" @echo "Cannon Absolute prestate hash: " @cat ./bin/prestate-proof.json | jq -r .pre - @echo "Cannon64 Absolute prestate hash: " + @echo "\nCannon64 Absolute prestate hash: " @cat ./bin/prestate-proof-mt64.json | jq -r .pre + @echo "\n-------------------- Experimental Prestates --------------------\n" @echo "CannonInterop Absolute prestate hash: " @cat ./bin/prestate-proof-interop.json | jq -r .pre + @echo "\nCannon64Next Absolute prestate hash: " + @cat ./bin/prestate-proof-mt64Next.json | jq -r .pre + @echo .PHONY: reproducible-prestate verify-reproducibility: diff --git a/op-program/repro.justfile b/op-program/repro.justfile index ced510206adb9..32a954479f5ac 100644 --- a/op-program/repro.justfile +++ b/op-program/repro.justfile @@ -55,6 +55,9 @@ prestate TYPE CLIENT_SUFFIX PRESTATE_SUFFIX: (client TYPE CLIENT_SUFFIX PRESTATE build-default: (prestate "singlethreaded-2" "" "") build-mt64: (prestate "multithreaded64-3" "64" "-mt64") +build-mt64Next: (prestate "multithreaded64-4" "64" "-mt64Next") build-interop: (prestate "multithreaded64-3" "-interop" "-interop") -build-all: build-default build-mt64 build-interop +build-all: build-default build-mt64 build-mt64Next build-interop +build-current: build-default build-mt64 build-interop +build-next: build-mt64Next diff --git a/ops/docker/op-stack-go/Dockerfile b/ops/docker/op-stack-go/Dockerfile index 39978945ee168..1b32afb29ef20 100644 --- a/ops/docker/op-stack-go/Dockerfile +++ b/ops/docker/op-stack-go/Dockerfile @@ -73,14 +73,17 @@ ARG TARGETOS FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.0.0 AS cannon-builder-v1-0-0 FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.2.0 AS cannon-builder-v1-2-0 FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.3.0 AS cannon-builder-v1-3-0 +FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.4.0 AS cannon-builder-v1-4-0 FROM --platform=$BUILDPLATFORM builder AS cannon-builder ARG CANNON_VERSION=v0.0.0 # Copy cannon binaries from previous versions COPY --from=cannon-builder-v1-0-0 /usr/local/bin/cannon ./cannon/multicannon/embeds/cannon-0 -COPY --from=cannon-builder-v1-2-0 /usr/local/bin/cannon-3 ./cannon/multicannon/embeds/cannon-3 COPY --from=cannon-builder-v1-3-0 /usr/local/bin/cannon-1 ./cannon/multicannon/embeds/cannon-1 +COPY --from=cannon-builder-v1-4-0 /usr/local/bin/cannon-2 ./cannon/multicannon/embeds/cannon-2 +COPY --from=cannon-builder-v1-2-0 /usr/local/bin/cannon-3 ./cannon/multicannon/embeds/cannon-3 COPY --from=cannon-builder-v1-3-0 /usr/local/bin/cannon-4 ./cannon/multicannon/embeds/cannon-4 +COPY --from=cannon-builder-v1-4-0 /usr/local/bin/cannon-5 ./cannon/multicannon/embeds/cannon-5 # Build current binaries 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" diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 9763b945f79a1..acd3ef778cba0 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -140,8 +140,8 @@ "sourceCodeHash": "0x856b07ed8bf3c6c11657af1fcea46cd4b8362fcaa6a9cb90cafe9274c3f8f23e" }, "src/cannon/MIPS64.sol:MIPS64": { - "initCodeHash": "0xb0d1143d94422953b1eb8b384cb93f83ee83d00c274ce7fb290ab9b02d89df9a", - "sourceCodeHash": "0x352b1d02660ebe9ea51af30cf10de0c941188806a334bf2cb0bc510464670e84" + "initCodeHash": "0xd38d92ed0ddeba1ecb7ae65dd4142a91b51c24c15f0e449f3c711e86b97db1ab", + "sourceCodeHash": "0x9c01b1c4611583819dbc4dfe529fc1d6ca5885620e89cc58668e0606d5bf959a" }, "src/cannon/PreimageOracle.sol:PreimageOracle": { "initCodeHash": "0x6af5b0e83b455aab8d0946c160a4dc049a4e03be69f8a2a9e87b574f27b25a66", diff --git a/packages/contracts-bedrock/src/cannon/MIPS64.sol b/packages/contracts-bedrock/src/cannon/MIPS64.sol index 51a6fa07d1c50..44e5bb842bf47 100644 --- a/packages/contracts-bedrock/src/cannon/MIPS64.sol +++ b/packages/contracts-bedrock/src/cannon/MIPS64.sol @@ -66,8 +66,8 @@ contract MIPS64 is ISemver { } /// @notice The semantic version of the MIPS64 contract. - /// @custom:semver 1.1.0 - string public constant version = "1.1.0"; + /// @custom:semver 1.2.0 + string public constant version = "1.2.0"; /// @notice The preimage oracle contract. IPreimageOracle internal immutable ORACLE; @@ -92,8 +92,8 @@ contract MIPS64 is ISemver { /// @param _oracle The address of the preimage oracle contract. constructor(IPreimageOracle _oracle, uint256 _stateVersion) { - // Supports VersionMultiThreaded64_v3 (v6) - if (_stateVersion != 6) { + // Supports VersionMultiThreaded64_v3 (6) and VersionMultiThreaded64_v4 (7) + if (_stateVersion != 6 && _stateVersion != 7) { revert UnsupportedStateVersion(); } ORACLE = _oracle; @@ -621,6 +621,10 @@ contract MIPS64 is ISemver { // ignored } else if (syscall_no == sys.SYS_LSEEK) { // ignored + } else if (syscall_no == sys.SYS_EVENTFD2) { + if (STATE_VERSION < 7) { + revert("MIPS64: unimplemented syscall"); + } } else { revert("MIPS64: unimplemented syscall"); } diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPS64Syscalls.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Syscalls.sol index 86f921e2821a3..cf29b09c5d7ed 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPS64Syscalls.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Syscalls.sol @@ -97,6 +97,7 @@ library MIPS64Syscalls { uint32 internal constant SYS_TGKILL = 5225; uint32 internal constant SYS_GETRLIMIT = 5095; uint32 internal constant SYS_LSEEK = 5008; + uint32 internal constant SYS_EVENTFD2 = 5284; // profiling-related syscalls - ignored uint32 internal constant SYS_SETITIMER = 5036; uint32 internal constant SYS_TIMERCREATE = 5216; diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol index 22f120e0ef6e6..cf788a10793a5 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol @@ -92,6 +92,7 @@ library MIPSSyscalls { uint32 internal constant SYS_TGKILL = 4266; uint32 internal constant SYS_GETRLIMIT = 4076; uint32 internal constant SYS_LSEEK = 4019; + uint32 internal constant SYS_EVENTFD2 = 4325; // profiling-related syscalls - ignored uint32 internal constant SYS_SETITIMER = 4104;