Commit fde52b49 authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Enable 64-bit tests (#12729)

* cannon: Set up fuzz tests to run across 32- and 64-bit VMs

Update preimage read fuzz test to work on 64-bit vms

* cannon: Run both 32- and 64-bit Cannon tests in CI

* cannon: Separate log and coverage files for 32- and 64-bit tests

* cannon: Clean up evm validation code

* cannon: Move skip utility to testutil

* cannon: Make TestState_EncodeWitness 64-bit compatible

* cannon: Skip failing 64-bit tests

* cannon: Update TestEVM_EmptyThreadStacks to work across 32- and 64-bit vms

* cannon: Update version state tests, add build tags

* cannon: Fix code coverage upload paths

* cannon: Fix code coverage file extensions

* cannon: Run 32/64-bit tests using a matrix

* cannon: Try to set better name for arch-specific cannon tests

* cannon: Use generated job names in bedrock requirements list

* cannon: Add comment on new validator util

---------
Co-authored-by: default avatarMatthew Slipper <me@matthewslipper.com>
parent 1b4fda30
......@@ -162,9 +162,9 @@ jobs:
description: Whether to notify on failure
type: boolean
default: false
mips64:
type: boolean
default: false
mips_word_size:
type: integer
default: 32
steps:
- checkout
- check-changed:
......@@ -187,28 +187,34 @@ jobs:
working_directory: cannon
- when:
condition:
not: <<parameters.mips64>>
equal: [32, <<parameters.mips_word_size>>]
steps:
- run:
name: Cannon Go 32-bit tests
command: |
export SKIP_SLOW_TESTS=<<parameters.skip_slow_tests>>
gotestsum --format=testname --junitfile=../tmp/test-results/cannon.xml --jsonfile=../tmp/testlogs/log.json \
-- -parallel=$(nproc) -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage.out ./...
gotestsum --format=testname --junitfile=../tmp/test-results/cannon-32.xml --jsonfile=../tmp/testlogs/log-32.json \
-- -parallel=$(nproc) -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage-32.out ./...
working_directory: cannon
- run:
name: Upload Cannon coverage
command: codecov --verbose --clean --flags cannon-go-tests-32 -f ./coverage-32.out
working_directory: cannon
- when:
condition: <<parameters.mips64>>
condition:
equal: [64, <<parameters.mips_word_size>>]
steps:
- run:
name: Cannon Go 64-bit tests
command: |
export SKIP_SLOW_TESTS=<<parameters.skip_slow_tests>>
gotestsum --format=testname --junitfile=../tmp/test-results/cannon.xml --jsonfile=../tmp/testlogs/log.json \
-- --tags=cannon64 -parallel=$(nproc) -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage.out ./...
gotestsum --format=testname --junitfile=../tmp/test-results/cannon-64.xml --jsonfile=../tmp/testlogs/log-64.json \
-- --tags=cannon64 -parallel=$(nproc) -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage-64.out ./...
working_directory: cannon
- run:
name: Upload Cannon coverage
command: codecov --verbose --clean --flags cannon-go-tests-64 -f ./coverage-64.out
working_directory: cannon
- run:
name: upload Cannon coverage
command: codecov --verbose --clean --flags cannon-go-tests
- store_test_results:
path: ./tmp/test-results
- store_artifacts:
......@@ -1365,7 +1371,8 @@ workflows:
- go-mod-download
- go-lint
- cannon-build-test-vectors
- cannon-go-lint-and-test
- cannon-go-lint-and-test-32-bit
- cannon-go-lint-and-test-64-bit
- check-generated-mocks-op-node
- check-generated-mocks-op-service
- go-mod-download
......@@ -1403,10 +1410,14 @@ workflows:
- check-generated-mocks-op-node
- check-generated-mocks-op-service
- cannon-go-lint-and-test:
name: cannon-go-lint-and-test-<<matrix.mips_word_size>>-bit
requires:
- contracts-bedrock-build
skip_slow_tests: true
notify: true
matrix:
parameters:
mips_word_size: [ 32, 64 ]
- cannon-build-test-vectors
- todo-issues:
name: todo-issues-check
......@@ -1605,10 +1616,14 @@ workflows:
- contracts-bedrock-build:
skip_pattern: test
- cannon-go-lint-and-test:
name: cannon-go-lint-and-test-<<matrix.mips_word_size>>-bit
requires:
- contracts-bedrock-build
context:
- slack
matrix:
parameters:
mips_word_size: [ 32, 64 ]
scheduled-docker-publish:
when:
......
......@@ -87,19 +87,28 @@ cannon-stf-verify:
fuzz:
printf "%s\n" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallBrk ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallMmap ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExitGroup ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallFcntl ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallBrk32 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallMmap32 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExitGroup32 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallFcntl32 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead32 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead32 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite32 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite32 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneST ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneMT32 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateConsistencyMulOp ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateConsistencyMultOp ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateConsistencyMultuOp ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallBrk64 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallMmap64 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallExitGroup64 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallFcntl64 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateHintRead64 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead64 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateHintWrite64 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite64 ./mipsevm/tests" \
"go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallCloneMT64 ./mipsevm/tests" \
| parallel -j 8 {}
.PHONY: \
......
......@@ -25,6 +25,10 @@ func setWitnessField(witness StateWitness, fieldOffset int, fieldData []byte) {
copy(witness[start:end], fieldData)
}
func setWitnessWord(witness StateWitness, fieldOffset int, value arch.Word) {
arch.ByteOrderWord.PutWord(witness[fieldOffset:], value)
}
// Run through all permutations of `exited` / `exitCode` and ensure that the
// correct witness, state hash, and VM Status is produced.
func TestState_EncodeWitness(t *testing.T) {
......@@ -70,27 +74,27 @@ func TestState_EncodeWitness(t *testing.T) {
expectedWitness := make(StateWitness, STATE_WITNESS_SIZE)
setWitnessField(expectedWitness, MEMROOT_WITNESS_OFFSET, memRoot[:])
setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:])
setWitnessField(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)})
setWitnessField(expectedWitness, HEAP_WITNESS_OFFSET, []byte{0, 0, 0, byte(heap)})
setWitnessWord(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, preimageOffset)
setWitnessWord(expectedWitness, HEAP_WITNESS_OFFSET, heap)
setWitnessField(expectedWitness, LL_RESERVATION_ACTIVE_OFFSET, []byte{1})
setWitnessField(expectedWitness, LL_ADDRESS_OFFSET, []byte{0, 0, 0, byte(llAddress)})
setWitnessField(expectedWitness, LL_OWNER_THREAD_OFFSET, []byte{0, 0, 0, byte(llThreadOwner)})
setWitnessWord(expectedWitness, LL_ADDRESS_OFFSET, llAddress)
setWitnessWord(expectedWitness, LL_OWNER_THREAD_OFFSET, llThreadOwner)
setWitnessField(expectedWitness, EXITCODE_WITNESS_OFFSET, []byte{c.exitCode})
if c.exited {
setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1})
}
setWitnessField(expectedWitness, STEP_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(step)})
setWitnessField(expectedWitness, STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(stepsSinceContextSwitch)})
setWitnessField(expectedWitness, WAKEUP_WITNESS_OFFSET, []byte{0xFF, 0xFF, 0xFF, 0xFF})
setWitnessWord(expectedWitness, WAKEUP_WITNESS_OFFSET, ^arch.Word(0))
setWitnessField(expectedWitness, TRAVERSE_RIGHT_WITNESS_OFFSET, []byte{0})
setWitnessField(expectedWitness, LEFT_THREADS_ROOT_WITNESS_OFFSET, leftStackRoot[:])
setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_WITNESS_OFFSET, rightStackRoot[:])
setWitnessField(expectedWitness, THREAD_ID_WITNESS_OFFSET, []byte{0, 0, 0, 1})
setWitnessWord(expectedWitness, THREAD_ID_WITNESS_OFFSET, 1)
// Validate witness
actualWitness, actualStateHash := state.EncodeWitness()
require.Equal(t, len(actualWitness), STATE_WITNESS_SIZE, "Incorrect witness size")
require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness")
require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness: \n\tactual = \t0x%x \n\texpected = \t0x%x", actualWitness, expectedWitness)
// Validate witness hash
expectedStateHash := crypto.Keccak256Hash(actualWitness)
expectedStateHash[0] = mipsevm.VmStatus(c.exited, c.exitCode)
......
//go:build !cannon64
// +build !cannon64
package singlethreaded
import (
......
//go:build !cannon64
// +build !cannon64
package singlethreaded
import (
......
......@@ -10,7 +10,6 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/stretchr/testify/require"
......@@ -37,6 +36,7 @@ func TestEVM(t *testing.T) {
for _, f := range testFiles {
testName := fmt.Sprintf("%v (%v)", f.Name(), c.Name)
t.Run(testName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
for _, skipped := range skipped {
if f.Name() == skipped {
t.Skipf("Skipping explicitly excluded open_mips testcase: %v", f.Name())
......@@ -47,10 +47,7 @@ func TestEVM(t *testing.T) {
// Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin"
expectPanic := strings.HasSuffix(f.Name(), "panic.bin")
evm := testutil.NewMIPSEVM(c.Contracts)
evm.SetLocalOracle(oracle)
testutil.LogStepFailureAtCleanup(t, evm)
validator := testutil.NewEvmValidator(t, c.StateHashFn, c.Contracts, testutil.WithLocalOracle(oracle))
fn := path.Join("open_mips_tests/test/bin", f.Name())
programMem, err := os.ReadFile(fn)
......@@ -88,12 +85,7 @@ func TestEVM(t *testing.T) {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep, c.StateHashFn)
// verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
goPost, _ := goVm.GetState().EncodeWitness()
require.Equalf(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM at step %d", state.GetStep())
validator.ValidateEVM(t, stepWitness, curStep, goVm)
}
if exitGroup {
require.NotEqual(t, arch.Word(testutil.EndAddr), goVm.GetState().GetPC(), "must not reach end")
......@@ -215,6 +207,7 @@ func TestEVMSingleStep_Operators(t *testing.T) {
for i, tt := range cases {
testName := fmt.Sprintf("%v (%v)", tt.name, v.Name)
t.Run(testName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4))
state := goVm.GetState()
var insn uint32
......@@ -316,6 +309,7 @@ func TestEVMSingleStep_LoadStore(t *testing.T) {
for _, v := range versions {
testName := fmt.Sprintf("%v (%v)", tt.name, v.Name)
t.Run(testName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
addr := tt.base + Word(tt.imm)
effAddr := arch.AddressMask & addr
......@@ -490,6 +484,7 @@ func TestEVMSingleStep_MulDiv(t *testing.T) {
for i, tt := range cases {
testName := fmt.Sprintf("%v (%v)", tt.name, v.Name)
t.Run(testName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4))
state := goVm.GetState()
var insn uint32
......@@ -864,6 +859,7 @@ func TestEVMFault(t *testing.T) {
for _, tt := range cases {
testName := fmt.Sprintf("%v (%v)", tt.name, v.Name)
t.Run(testName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithPC(tt.pc), testutil.WithNextPC(tt.nextPC))
state := goVm.GetState()
testutil.StoreInstruction(state.GetMemory(), 0, tt.insn)
......@@ -886,8 +882,7 @@ func TestHelloEVM(t *testing.T) {
v := v
t.Run(v.Name, func(t *testing.T) {
t.Parallel()
evm := testutil.NewMIPSEVM(v.Contracts)
testutil.LogStepFailureAtCleanup(t, evm)
validator := testutil.NewEvmValidator(t, v.StateHashFn, v.Contracts)
var stdOutBuf, stdErrBuf bytes.Buffer
elfFile := testutil.ProgramPath("hello")
......@@ -907,12 +902,7 @@ func TestHelloEVM(t *testing.T) {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
// verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
goPost, _ := goVm.GetState().EncodeWitness()
require.Equalf(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM. insn: %x", insn)
validator.ValidateEVM(t, stepWitness, step, goVm)
}
end := time.Now()
delta := end.Sub(start)
......@@ -935,9 +925,7 @@ func TestClaimEVM(t *testing.T) {
v := v
t.Run(v.Name, func(t *testing.T) {
t.Parallel()
evm := testutil.NewMIPSEVM(v.Contracts)
testutil.LogStepFailureAtCleanup(t, evm)
validator := testutil.NewEvmValidator(t, v.StateHashFn, v.Contracts)
oracle, expectedStdOut, expectedStdErr := testutil.ClaimTestOracle(t)
var stdOutBuf, stdErrBuf bytes.Buffer
......@@ -958,12 +946,7 @@ func TestClaimEVM(t *testing.T) {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
validator.ValidateEVM(t, stepWitness, curStep, goVm)
}
require.True(t, state.GetExited(), "must complete program")
......@@ -983,8 +966,7 @@ func TestEntryEVM(t *testing.T) {
v := v
t.Run(v.Name, func(t *testing.T) {
t.Parallel()
evm := testutil.NewMIPSEVM(v.Contracts)
testutil.LogStepFailureAtCleanup(t, evm)
validator := testutil.NewEvmValidator(t, v.StateHashFn, v.Contracts)
var stdOutBuf, stdErrBuf bytes.Buffer
elfFile := testutil.ProgramPath("entry")
......@@ -992,7 +974,7 @@ func TestEntryEVM(t *testing.T) {
state := goVm.GetState()
start := time.Now()
for i := 0; i < 400_000; i++ {
for i := 0; i < 500_000; i++ {
curStep := goVm.GetState().GetStep()
if goVm.GetState().GetExited() {
break
......@@ -1004,11 +986,7 @@ func TestEntryEVM(t *testing.T) {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn)
// verify the post-state matches.
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
validator.ValidateEVM(t, stepWitness, curStep, goVm)
}
end := time.Now()
delta := end.Sub(start)
......@@ -1090,6 +1068,7 @@ func TestEVMSingleStepBranch(t *testing.T) {
for i, tt := range cases {
testName := fmt.Sprintf("%v (%v)", tt.name, v.Name)
t.Run(testName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(tt.pc))
state := goVm.GetState()
const rsReg = 8 // t0
......
......@@ -8,7 +8,6 @@ import (
"slices"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
......@@ -243,6 +242,7 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
effAddr := arch.AddressMask & c.addr
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageValue)
......@@ -339,6 +339,7 @@ func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
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()
......@@ -409,31 +410,25 @@ func TestEVM_SysClone_FlagHandling(t *testing.T) {
var err error
var stepWitness *mipsevm.StepWitness
us := multithreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil, nil)
goVm := multithreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil, nil)
if !c.valid {
// The VM should exit
stepWitness, err = us.Step(true)
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
require.Equal(t, curStep+1, state.GetStep())
require.Equal(t, true, us.GetState().GetExited())
require.Equal(t, uint8(mipsevm.VMStatusPanic), us.GetState().GetExitCode())
require.Equal(t, true, goVm.GetState().GetExited())
require.Equal(t, uint8(mipsevm.VMStatusPanic), goVm.GetState().GetExitCode())
require.Equal(t, 1, state.ThreadCount())
} else {
stepWitness, err = us.Step(true)
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
require.Equal(t, curStep+1, state.GetStep())
require.Equal(t, false, us.GetState().GetExited())
require.Equal(t, uint8(0), us.GetState().GetExitCode())
require.Equal(t, false, goVm.GetState().GetExited())
require.Equal(t, uint8(0), goVm.GetState().GetExitCode())
require.Equal(t, 2, state.ThreadCount())
}
evm := testutil.NewMIPSEVM(contracts)
testutil.LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, curStep, multithreaded.GetStateHashFn())
goPost, _ := us.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
testutil.ValidateEVM(t, stepWitness, curStep, goVm, multithreaded.GetStateHashFn(), contracts)
})
}
}
......@@ -660,6 +655,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm, state, contracts := setup(t, i*1234, nil)
step := state.GetStep()
......@@ -729,6 +725,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm, state, contracts := setup(t, i*1122, nil)
mttestutil.SetupThreads(int64(i*2244), state, c.traverseRight, c.activeThreadCount, c.inactiveThreadCount)
step := state.Step
......@@ -1109,6 +1106,7 @@ var NoopSyscalls = map[string]uint32{
func TestEVM_NoopSyscall(t *testing.T) {
for noopName, noopVal := range NoopSyscalls {
t.Run(noopName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm, state, contracts := setup(t, int(noopVal), nil)
testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn)
......@@ -1155,6 +1153,7 @@ func TestEVM_UnsupportedSyscall(t *testing.T) {
i := i
syscallNum := syscallNum
t.Run(testName, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
t.Parallel()
goVm, state, contracts := setup(t, i*3434, nil)
// Setup basic getThreadId syscall instruction
......@@ -1196,7 +1195,7 @@ func TestEVM_EmptyThreadStacks(t *testing.T) {
require.PanicsWithValue(t, "Active thread stack is empty", func() { _, _ = goVm.Step(false) })
errorMessage := "MIPS2: active thread stack is empty"
errorMessage := "active thread stack is empty"
testutil.AssertEVMReverts(t, state, contracts, tracer, proofCase.Proof, testutil.CreateErrorStringMatcher(errorMessage))
})
}
......
//go:build !cannon64
// +build !cannon64
package tests
import (
......
......@@ -19,11 +19,20 @@ import (
const syscallInsn = uint32(0x00_00_00_0c)
func FuzzStateSyscallBrk(f *testing.F) {
func FuzzStateSyscallBrk32(f *testing.F) {
doFuzzStateSyscallBrk(f)
}
func FuzzStateSyscallBrk64(f *testing.F) {
doFuzzStateSyscallBrk(f)
}
func doFuzzStateSyscallBrk(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed))
state := goVm.GetState()
state.GetRegistersRef()[2] = arch.SysBrk
......@@ -48,7 +57,15 @@ func FuzzStateSyscallBrk(f *testing.F) {
})
}
func FuzzStateSyscallMmap(f *testing.F) {
func FuzzStateSyscallMmap32(f *testing.F) {
doFuzzStateSyscallMmap(f)
}
func FuzzStateSyscallMmap64(f *testing.F) {
doFuzzStateSyscallMmap(f)
}
func doFuzzStateSyscallMmap(f *testing.F) {
// Add special cases for large memory allocation
f.Add(Word(0), Word(0x1000), Word(program.HEAP_END), int64(1))
f.Add(Word(0), Word(1<<31), Word(program.HEAP_START), int64(2))
......@@ -59,6 +76,7 @@ func FuzzStateSyscallMmap(f *testing.F) {
f.Fuzz(func(t *testing.T, addr Word, siz Word, heap Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
testutil.WithRandomization(seed), testutil.WithHeap(heap))
state := goVm.GetState()
......@@ -103,11 +121,20 @@ func FuzzStateSyscallMmap(f *testing.F) {
})
}
func FuzzStateSyscallExitGroup(f *testing.F) {
func FuzzStateSyscallExitGroup32(f *testing.F) {
doFuzzStateSyscallExitGroup(f)
}
func FuzzStateSyscallExitGroup64(f *testing.F) {
doFuzzStateSyscallExitGroup(f)
}
func doFuzzStateSyscallExitGroup(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, exitCode uint8, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
testutil.WithRandomization(seed))
state := goVm.GetState()
......@@ -132,11 +159,20 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
})
}
func FuzzStateSyscallFcntl(f *testing.F) {
func FuzzStateSyscallFcntl32(f *testing.F) {
doFuzzStateSyscallFcntl(f)
}
func FuzzStateSyscallFcntl64(f *testing.F) {
doFuzzStateSyscallFcntl(f)
}
func doFuzzStateSyscallFcntl(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, fd Word, cmd Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
testutil.WithRandomization(seed))
state := goVm.GetState()
......@@ -188,11 +224,20 @@ func FuzzStateSyscallFcntl(f *testing.F) {
})
}
func FuzzStateHintRead(f *testing.F) {
func FuzzStateHintRead32(f *testing.F) {
doFuzzStateHintRead(f)
}
func FuzzStateHintRead64(f *testing.F) {
doFuzzStateHintRead(f)
}
func doFuzzStateHintRead(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr Word, count Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
preimageData := []byte("hello world")
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageData) // only used for hinting
......@@ -225,14 +270,22 @@ func FuzzStateHintRead(f *testing.F) {
})
}
func FuzzStatePreimageRead(f *testing.F) {
func FuzzStatePreimageRead32(f *testing.F) {
doFuzzStatePreimageRead(f)
}
func FuzzStatePreimageRead64(f *testing.F) {
doFuzzStatePreimageRead(f)
}
func doFuzzStatePreimageRead(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr arch.Word, pc arch.Word, count arch.Word, preimageOffset arch.Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
effAddr := addr & arch.AddressMask
pc = pc & arch.AddressMask
preexistingMemoryVal := [4]byte{0xFF, 0xFF, 0xFF, 0xFF}
preexistingMemoryVal := ^arch.Word(0)
preimageValue := []byte("hello world")
preimageData := testutil.AddPreimageLengthPrefix(preimageValue)
if preimageOffset >= Word(len(preimageData)) || pc == effAddr {
......@@ -249,11 +302,11 @@ func FuzzStatePreimageRead(f *testing.F) {
state.GetRegistersRef()[5] = addr
state.GetRegistersRef()[6] = count
testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn)
state.GetMemory().SetWord(effAddr, arch.ByteOrderWord.Word(preexistingMemoryVal[:]))
state.GetMemory().SetWord(effAddr, preexistingMemoryVal)
step := state.GetStep()
alignment := addr & arch.ExtMask
writeLen := 4 - alignment
writeLen := arch.WordSizeBytes - alignment
if count < writeLen {
writeLen = count
}
......@@ -272,7 +325,8 @@ func FuzzStatePreimageRead(f *testing.F) {
expected.PreimageOffset += writeLen
if writeLen > 0 {
// Expect a memory write
expectedMemory := preexistingMemoryVal
var expectedMemory []byte
expectedMemory = arch.ByteOrderWord.AppendWord(expectedMemory, preexistingMemoryVal)
copy(expectedMemory[alignment:], preimageData[preimageOffset:preimageOffset+writeLen])
expected.ExpectMemoryWriteWord(effAddr, arch.ByteOrderWord.Word(expectedMemory[:]))
}
......@@ -288,11 +342,20 @@ func FuzzStatePreimageRead(f *testing.F) {
})
}
func FuzzStateHintWrite(f *testing.F) {
func FuzzStateHintWrite32(f *testing.F) {
doFuzzStateHintWrite(f)
}
func FuzzStateHintWrite64(f *testing.F) {
doFuzzStateHintWrite(f)
}
func doFuzzStateHintWrite(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr Word, count Word, hint1, hint2, hint3 []byte, randSeed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
// Make sure pc does not overlap with hint data in memory
pc := Word(0)
if addr <= 8 {
......@@ -370,11 +433,20 @@ func FuzzStateHintWrite(f *testing.F) {
})
}
func FuzzStatePreimageWrite(f *testing.F) {
func FuzzStatePreimageWrite32(f *testing.F) {
doFuzzStatePreimageWrite(f)
}
func FuzzStatePreimageWrite64(f *testing.F) {
doFuzzStatePreimageWrite(f)
}
func doFuzzStatePreimageWrite(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr arch.Word, count arch.Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
testutil.TemporarilySkip64BitTests(t)
// Make sure pc does not overlap with preimage data in memory
pc := Word(0)
if addr <= 8 {
......
......@@ -13,9 +13,18 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
func FuzzStateSyscallCloneMT(f *testing.F) {
func FuzzStateSyscallCloneMT32(f *testing.F) {
doFuzzStateSyscallCloneMT(f)
}
func FuzzStateSyscallCloneMT64(f *testing.F) {
doFuzzStateSyscallCloneMT(f)
}
func doFuzzStateSyscallCloneMT(f *testing.F) {
v := GetMultiThreadedTestCase(f)
f.Fuzz(func(t *testing.T, nextThreadId, stackPtr Word, seed int64) {
testutil.TemporarilySkip64BitTests(t)
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
......
//go:build !cannon64
// +build !cannon64
package tests
import (
"os"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
......@@ -32,11 +34,6 @@ func FuzzStateSyscallCloneST(f *testing.F) {
require.False(t, stepWitness.HasPreimage())
expected.Validate(t, state)
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
......@@ -40,7 +40,7 @@ type MIPSEVM struct {
lastPreimageOracleInput []byte
}
func NewMIPSEVM(contracts *ContractMetadata, opts ...evmOption) *MIPSEVM {
func newMIPSEVM(contracts *ContractMetadata, opts ...evmOption) *MIPSEVM {
env, evmState := NewEVMEnv(contracts)
sender := vm.AccountRef{0x13, 0x37}
startingGas := uint64(maxStepGas)
......@@ -65,6 +65,12 @@ func WithTracingHooks(tracer *tracing.Hooks) evmOption {
}
}
func WithLocalOracle(oracle mipsevm.PreimageOracle) evmOption {
return func(evm *MIPSEVM) {
evm.SetLocalOracle(oracle)
}
}
func (m *MIPSEVM) SetTracer(tracer *tracing.Hooks) {
m.env.Config.Tracer = tracer
}
......@@ -192,17 +198,35 @@ func LogStepFailureAtCleanup(t *testing.T, mipsEvm *MIPSEVM) {
})
}
// ValidateEVM runs a single evm step and validates against an FPVM poststate
func ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM, hashFn mipsevm.HashFn, contracts *ContractMetadata, opts ...evmOption) {
evm := NewMIPSEVM(contracts, opts...)
type EvmValidator struct {
evm *MIPSEVM
hashFn mipsevm.HashFn
}
// NewEvmValidator creates a validator that can be run repeatedly across multiple steps
func NewEvmValidator(t *testing.T, hashFn mipsevm.HashFn, contracts *ContractMetadata, opts ...evmOption) *EvmValidator {
evm := newMIPSEVM(contracts, opts...)
LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, step, hashFn)
return &EvmValidator{
evm: evm,
hashFn: hashFn,
}
}
func (v *EvmValidator) ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM) {
evmPost := v.evm.Step(t, stepWitness, step, v.hashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
// ValidateEVM runs a single evm step and validates against an FPVM poststate
func ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM, hashFn mipsevm.HashFn, contracts *ContractMetadata, opts ...evmOption) {
validator := NewEvmValidator(t, hashFn, contracts)
validator.ValidateEVM(t, stepWitness, step, goVm)
}
type ErrMatcher func(*testing.T, []byte)
func CreateNoopErrorMatcher() ErrMatcher {
......@@ -250,7 +274,7 @@ func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *Contract
}
func AssertPreimageOracleReverts(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset arch.Word, contracts *ContractMetadata, opts ...evmOption) {
evm := NewMIPSEVM(contracts, opts...)
evm := newMIPSEVM(contracts, opts...)
LogStepFailureAtCleanup(t, evm)
evm.assertPreimageOracleReverts(t, preimageKey, preimageValue, preimageOffset)
......
......@@ -141,3 +141,10 @@ func RunVMTest_Claim[T mipsevm.FPVMState](t *testing.T, initState program.Create
require.Equal(t, expectedStdOut, stdOutBuf.String(), "stdout")
require.Equal(t, expectedStdErr, stdErrBuf.String(), "stderr")
}
func TemporarilySkip64BitTests(t *testing.T) {
if !arch.IsMips32 {
// TODO(#12598) Update and enable these tests
t.Skip("Skipping 64-bit test")
}
}
//go:build cannon64
// +build cannon64
package versions
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/op-service/serialize"
)
func TestNewFromState(t *testing.T) {
t.Run("multithreaded64", func(t *testing.T) {
actual, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err)
require.IsType(t, &multithreaded.State{}, actual.FPVMState)
require.Equal(t, VersionMultiThreaded64, actual.Version)
})
}
func TestLoadStateFromFile(t *testing.T) {
t.Run("Multithreaded64FromBinary", func(t *testing.T) {
expected, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err)
path := writeToFile(t, "state.bin.gz", expected)
actual, err := LoadStateFromFile(path)
require.NoError(t, err)
require.Equal(t, expected, actual)
})
}
func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
tests := []struct {
version StateVersion
createState func() mipsevm.FPVMState
}{
{VersionMultiThreaded64, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
}
for _, test := range tests {
test := test
t.Run(test.version.String(), func(t *testing.T) {
state, err := NewFromState(test.createState())
require.NoError(t, err)
dir := t.TempDir()
path := filepath.Join(dir, "test.json")
err = serialize.Write(path, state, 0o644)
require.ErrorIs(t, err, ErrJsonNotSupported)
})
}
}
func writeToFile(t *testing.T, filename string, data serialize.Serializable) string {
dir := t.TempDir()
path := filepath.Join(dir, filename)
require.NoError(t, serialize.Write(path, data, 0o644))
return path
}
//go:build !cannon64
// +build !cannon64
package versions
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-service/serialize"
"github.com/stretchr/testify/require"
)
func TestNewFromState(t *testing.T) {
......@@ -49,10 +53,6 @@ func TestLoadStateFromFile(t *testing.T) {
})
}
func TestLoadStateFromFile64(t *testing.T) {
t.Skip("TODO(#12205): Test asserting that cannon64 fails to decode a 32-bit state")
}
func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
tests := []struct {
version StateVersion
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment