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

cannon: Add MTCannon-specific differential tests (#11605)

* cannon: Implement multithreaded clone fuzz test

* cannon: Add more clone evm tests

* cannon: Add evm test for GetTID syscall

* cannon: Add evm test for SysExit

* cannon: Add evm test for popping exited threads from the stack

* cannon: Fix futex wait handling, add evm test

* cannon: Add evm test for handling waiting thread

* cannon: Add test utils for defining / validating MTState expectations

* cannon: Add tests for futex wake, wake traversal

* cannon: Add test for SysYield

* cannon: Add SysOpen test, todos

* cannon: Add test for SchedQuantum preemption, fix inconsistency

* cannon: Add tests for noop, unsupported syscalls

* cannon: Remove duplicate constants

* cannon: Add tests for unsupported futex ops

* cannon: Group traversal tests, fix TestEVM_WakeupTraversalStep

* cannon: Add tests for nanosleep

* cannon: Add additional testcase for wakeup traversal

* cannon: Tweak futex wake tests

* cannon: Update mt fuzz test to use new test utils

* cannon: Rename contructor method for consistency

* cannon: Add some simple tests for ExpectedMTState util

* cannon: Add another validation test

* cannon: Move syscall lists to tests where they're used

* cannon: Add comment

* cannon: Extract some evm test helpers

* cannon: Cleanup - use require.Equalf for formatting

* cannon: Rename test util to AssertEVMReverts

* cannon: Add GetThreadStacks helper

* cannon: Add a few more traversal tests
parent aebf669c
......@@ -15,7 +15,6 @@ import (
// Syscall codes
const (
SysMmap = 4090
SysMunmap = 4091
SysBrk = 4045
SysClone = 4120
SysExitGroup = 4246
......@@ -32,6 +31,7 @@ const (
// Noop Syscall codes
const (
SysMunmap = 4091
SysGetAffinity = 4240
SysMadvise = 4218
SysRtSigprocmask = 4195
......
......@@ -102,13 +102,13 @@ func (m *InstrumentedState) handleSyscall() error {
// args: a0 = addr, a1 = op, a2 = val, a3 = timeout
switch a1 {
case exec.FutexWaitPrivate:
thread.FutexAddr = a0
m.memoryTracker.TrackMemAccess(a0)
mem := m.state.Memory.GetMemory(a0)
if mem != a2 {
v0 = exec.SysErrorSignal
v1 = exec.MipsEAGAIN
} else {
thread.FutexAddr = a0
thread.FutexVal = a2
if a3 == 0 {
thread.FutexTimeoutStep = exec.FutexNoTimeout
......@@ -242,11 +242,11 @@ func (m *InstrumentedState) mipsStep() error {
if m.state.StepsSinceLastContextSwitch >= exec.SchedQuantum {
// Force a context switch as this thread has been active too long
if m.state.threadCount() > 1 {
if m.state.ThreadCount() > 1 {
// Log if we're hitting our context switch limit - only matters if we have > 1 thread
if m.log.Enabled(context.Background(), log.LevelTrace) {
msg := fmt.Sprintf("Thread has reached maximum execution steps (%v) - preempting.", exec.SchedQuantum)
m.log.Trace(msg, "threadId", thread.ThreadId, "threadCount", m.state.threadCount(), "pc", thread.Cpu.PC)
m.log.Trace(msg, "threadId", thread.ThreadId, "threadCount", m.state.ThreadCount(), "pc", thread.Cpu.PC)
}
}
m.preemptThread(thread)
......@@ -339,5 +339,5 @@ func (m *InstrumentedState) popThread() {
}
func (m *InstrumentedState) lastThreadRemaining() bool {
return m.state.threadCount() == 1
return m.state.ThreadCount() == 1
}
......@@ -215,7 +215,7 @@ func (s *State) EncodeThreadProof() []byte {
return out
}
func (s *State) threadCount() int {
func (s *State) ThreadCount() int {
return len(s.LeftThreadStack) + len(s.RightThreadStack)
}
......
package testutil
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
)
// ExpectedMTState is a test utility that basically stores a copy of a state that can be explicitly mutated
// to define an expected post-state. The post-state is then validated with ExpectedMTState.Validate(t, postState)
type ExpectedMTState struct {
PreimageKey common.Hash
PreimageOffset uint32
Heap uint32
ExitCode uint8
Exited bool
Step uint64
LastHint hexutil.Bytes
MemoryRoot common.Hash
// Threading-related expectations
StepsSinceLastContextSwitch uint64
Wakeup uint32
TraverseRight bool
NextThreadId uint32
ThreadCount int
RightStackSize int
LeftStackSize int
prestateActiveThreadId uint32
prestateActiveThreadOrig ExpectedThreadState // Cached for internal use
ActiveThreadId uint32
threadExpectations map[uint32]*ExpectedThreadState
}
type ExpectedThreadState struct {
ThreadId uint32
ExitCode uint8
Exited bool
FutexAddr uint32
FutexVal uint32
FutexTimeoutStep uint64
PC uint32
NextPC uint32
HI uint32
LO uint32
Registers [32]uint32
Dropped bool
}
func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
currentThread := fromState.GetCurrentThread()
expectedThreads := make(map[uint32]*ExpectedThreadState)
for _, t := range GetAllThreads(fromState) {
expectedThreads[t.ThreadId] = newExpectedThreadState(t)
}
return &ExpectedMTState{
// General Fields
PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(),
Heap: fromState.GetHeap(),
ExitCode: fromState.GetExitCode(),
Exited: fromState.GetExited(),
Step: fromState.GetStep(),
LastHint: fromState.GetLastHint(),
MemoryRoot: fromState.GetMemory().MerkleRoot(),
// Thread-related global fields
StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch,
Wakeup: fromState.Wakeup,
TraverseRight: fromState.TraverseRight,
NextThreadId: fromState.NextThreadId,
ThreadCount: fromState.ThreadCount(),
RightStackSize: len(fromState.RightThreadStack),
LeftStackSize: len(fromState.LeftThreadStack),
// ThreadState expectations
prestateActiveThreadId: currentThread.ThreadId,
prestateActiveThreadOrig: *newExpectedThreadState(currentThread), // Cache prestate thread for internal use
ActiveThreadId: currentThread.ThreadId,
threadExpectations: expectedThreads,
}
}
func newExpectedThreadState(fromThread *multithreaded.ThreadState) *ExpectedThreadState {
return &ExpectedThreadState{
ThreadId: fromThread.ThreadId,
ExitCode: fromThread.ExitCode,
Exited: fromThread.Exited,
FutexAddr: fromThread.FutexAddr,
FutexVal: fromThread.FutexVal,
FutexTimeoutStep: fromThread.FutexTimeoutStep,
PC: fromThread.Cpu.PC,
NextPC: fromThread.Cpu.NextPC,
HI: fromThread.Cpu.HI,
LO: fromThread.Cpu.LO,
Registers: fromThread.Registers,
Dropped: false,
}
}
func (e *ExpectedMTState) ExpectStep() {
// Set some standard expectations for a normal step
e.Step += 1
e.PrestateActiveThread().PC += 4
e.PrestateActiveThread().NextPC += 4
e.StepsSinceLastContextSwitch += 1
}
func (e *ExpectedMTState) ExpectPreemption(preState *multithreaded.State) {
e.ActiveThreadId = FindNextThread(preState).ThreadId
e.StepsSinceLastContextSwitch = 0
if preState.TraverseRight {
e.TraverseRight = e.RightStackSize > 1
e.RightStackSize -= 1
e.LeftStackSize += 1
} else {
e.TraverseRight = e.LeftStackSize == 1
e.LeftStackSize -= 1
e.RightStackSize += 1
}
}
func (e *ExpectedMTState) ExpectNewThread() *ExpectedThreadState {
newThreadId := e.NextThreadId
e.NextThreadId += 1
e.ThreadCount += 1
// Clone expectations from prestate active thread's original state (bf changing any expectations)
newThread := &ExpectedThreadState{}
*newThread = e.prestateActiveThreadOrig
newThread.ThreadId = newThreadId
e.threadExpectations[newThreadId] = newThread
return newThread
}
func (e *ExpectedMTState) ActiveThread() *ExpectedThreadState {
return e.threadExpectations[e.ActiveThreadId]
}
func (e *ExpectedMTState) PrestateActiveThread() *ExpectedThreadState {
return e.threadExpectations[e.prestateActiveThreadId]
}
func (e *ExpectedMTState) Thread(threadId uint32) *ExpectedThreadState {
return e.threadExpectations[threadId]
}
func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreaded.State) {
require.Equalf(t, e.PreimageKey, actualState.GetPreimageKey(), "Expect preimageKey = %v", e.PreimageKey)
require.Equalf(t, e.PreimageOffset, actualState.GetPreimageOffset(), "Expect preimageOffset = %v", e.PreimageOffset)
require.Equalf(t, e.Heap, actualState.GetHeap(), "Expect heap = 0x%x", e.Heap)
require.Equalf(t, e.ExitCode, actualState.GetExitCode(), "Expect exitCode = 0x%x", e.ExitCode)
require.Equalf(t, e.Exited, actualState.GetExited(), "Expect exited = %v", e.Exited)
require.Equalf(t, e.Step, actualState.GetStep(), "Expect step = %d", e.Step)
require.Equalf(t, e.LastHint, actualState.GetLastHint(), "Expect lastHint = %v", e.LastHint)
require.Equalf(t, e.MemoryRoot, common.Hash(actualState.GetMemory().MerkleRoot()), "Expect memory root = %v", e.MemoryRoot)
// Thread-related global fields
require.Equalf(t, e.StepsSinceLastContextSwitch, actualState.StepsSinceLastContextSwitch, "Expect StepsSinceLastContextSwitch = %v", e.StepsSinceLastContextSwitch)
require.Equalf(t, e.Wakeup, actualState.Wakeup, "Expect Wakeup = %v", e.Wakeup)
require.Equalf(t, e.TraverseRight, actualState.TraverseRight, "Expect TraverseRight = %v", e.TraverseRight)
require.Equalf(t, e.NextThreadId, actualState.NextThreadId, "Expect NextThreadId = %v", e.NextThreadId)
require.Equalf(t, e.ThreadCount, actualState.ThreadCount(), "Expect thread count = %v", e.ThreadCount)
require.Equalf(t, e.RightStackSize, len(actualState.RightThreadStack), "Expect right stack size = %v", e.RightStackSize)
require.Equalf(t, e.LeftStackSize, len(actualState.LeftThreadStack), "Expect right stack size = %v", e.LeftStackSize)
// Check active thread
activeThread := actualState.GetCurrentThread()
require.Equal(t, e.ActiveThreadId, activeThread.ThreadId)
// Check all threads
expectedThreadCount := 0
for tid, exp := range e.threadExpectations {
actualThread := FindThread(actualState, tid)
isActive := tid == activeThread.ThreadId
if exp.Dropped {
require.Nil(t, actualThread, "Thread %v should have been dropped", tid)
} else {
require.NotNil(t, actualThread, "Could not find thread matching expected thread with id %v", tid)
e.validateThread(t, exp, actualThread, isActive)
expectedThreadCount++
}
}
require.Equal(t, expectedThreadCount, actualState.ThreadCount(), "Thread expectations do not match thread count")
}
func (e *ExpectedMTState) validateThread(t require.TestingT, et *ExpectedThreadState, actual *multithreaded.ThreadState, isActive bool) {
threadInfo := fmt.Sprintf("tid = %v, active = %v", actual.ThreadId, isActive)
require.Equalf(t, et.ThreadId, actual.ThreadId, "Expect ThreadId = 0x%x (%v)", et.ThreadId, threadInfo)
require.Equalf(t, et.PC, actual.Cpu.PC, "Expect PC = 0x%x (%v)", et.PC, threadInfo)
require.Equalf(t, et.NextPC, actual.Cpu.NextPC, "Expect nextPC = 0x%x (%v)", et.NextPC, threadInfo)
require.Equalf(t, et.HI, actual.Cpu.HI, "Expect HI = 0x%x (%v)", et.HI, threadInfo)
require.Equalf(t, et.LO, actual.Cpu.LO, "Expect LO = 0x%x (%v)", et.LO, threadInfo)
require.Equalf(t, et.Registers, actual.Registers, "Expect registers to match (%v)", threadInfo)
require.Equalf(t, et.ExitCode, actual.ExitCode, "Expect exitCode = %v (%v)", et.ExitCode, threadInfo)
require.Equalf(t, et.Exited, actual.Exited, "Expect exited = %v (%v)", et.Exited, threadInfo)
require.Equalf(t, et.FutexAddr, actual.FutexAddr, "Expect futexAddr = %v (%v)", et.FutexAddr, threadInfo)
require.Equalf(t, et.FutexVal, actual.FutexVal, "Expect futexVal = %v (%v)", et.FutexVal, threadInfo)
require.Equalf(t, et.FutexTimeoutStep, actual.FutexTimeoutStep, "Expect futexTimeoutStep = %v (%v)", et.FutexTimeoutStep, threadInfo)
}
package testutil
import (
"fmt"
"testing"
//"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
)
type ExpectationMutator func(e *ExpectedMTState, st *multithreaded.State)
func TestValidate_shouldCatchMutations(t *testing.T) {
states := []*multithreaded.State{
RandomState(0),
RandomState(1),
RandomState(2),
}
var emptyHash [32]byte
someThread := RandomThread(123)
cases := []struct {
name string
mut ExpectationMutator
}{
{name: "PreimageKey", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageKey = emptyHash }},
{name: "PreimageOffset", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageOffset += 1 }},
{name: "Heap", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Heap += 1 }},
{name: "ExitCode", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ExitCode += 1 }},
{name: "Exited", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Exited = !e.Exited }},
{name: "Step", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Step += 1 }},
{name: "LastHint", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LastHint = []byte{7, 8, 9, 10} }},
{name: "MemoryRoot", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.MemoryRoot = emptyHash }},
{name: "StepsSinceLastContextSwitch", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.StepsSinceLastContextSwitch += 1 }},
{name: "Wakeup", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Wakeup += 1 }},
{name: "TraverseRight", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.TraverseRight = !e.TraverseRight }},
{name: "NextThreadId", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.NextThreadId += 1 }},
{name: "ThreadCount", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ThreadCount += 1 }},
{name: "RightStackSize", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.RightStackSize += 1 }},
{name: "LeftStackSize", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LeftStackSize += 1 }},
{name: "ActiveThreadId", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ActiveThreadId += 1 }},
{name: "Empty thread expectations", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations = map[uint32]*ExpectedThreadState{}
}},
{name: "Mismatched thread expectations", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations = map[uint32]*ExpectedThreadState{someThread.ThreadId: newExpectedThreadState(someThread)}
}},
{name: "Active threadId", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].ThreadId += 1
}},
{name: "Active thread exitCode", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].ExitCode += 1
}},
{name: "Active thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].Exited = !st.GetCurrentThread().Exited
}},
{name: "Active thread futexAddr", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].FutexAddr += 1
}},
{name: "Active thread futexVal", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].FutexVal += 1
}},
{name: "Active thread FutexTimeoutStep", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].FutexTimeoutStep += 1
}},
{name: "Active thread PC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].PC += 1
}},
{name: "Active thread NextPC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].NextPC += 1
}},
{name: "Active thread HI", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].HI += 1
}},
{name: "Active thread LO", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].LO += 1
}},
{name: "Active thread Registers", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].Registers[0] += 1
}},
{name: "Active thread dropped", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].Dropped = true
}},
{name: "Inactive threadId", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].ThreadId += 1
}},
{name: "Inactive thread exitCode", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].ExitCode += 1
}},
{name: "Inactive thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].Exited = !FindNextThread(st).Exited
}},
{name: "Inactive thread futexAddr", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].FutexAddr += 1
}},
{name: "Inactive thread futexVal", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].FutexVal += 1
}},
{name: "Inactive thread FutexTimeoutStep", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].FutexTimeoutStep += 1
}},
{name: "Inactive thread PC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].PC += 1
}},
{name: "Inactive thread NextPC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].NextPC += 1
}},
{name: "Inactive thread HI", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].HI += 1
}},
{name: "Inactive thread LO", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].LO += 1
}},
{name: "Inactive thread Registers", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].Registers[0] += 1
}},
{name: "Inactive thread dropped", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].Dropped = true
}},
}
for _, c := range cases {
for i, state := range states {
testName := fmt.Sprintf("%v (state #%v)", c.name, i)
t.Run(testName, func(t *testing.T) {
expected := NewExpectedMTState(state)
c.mut(expected, state)
// We should detect the change and fail
mockT := &MockTestingT{}
expected.Validate(mockT, state)
mockT.RequireFailed(t)
})
}
}
}
func TestValidate_shouldPassUnchangedExpectations(t *testing.T) {
states := []*multithreaded.State{
RandomState(0),
RandomState(1),
RandomState(2),
}
for i, state := range states {
testName := fmt.Sprintf("State #%v", i)
t.Run(testName, func(t *testing.T) {
expected := NewExpectedMTState(state)
mockT := &MockTestingT{}
expected.Validate(mockT, state)
mockT.RequireNoFailure(t)
})
}
}
type MockTestingT struct {
errCount int
}
var _ require.TestingT = (*MockTestingT)(nil)
func (m *MockTestingT) Errorf(format string, args ...interface{}) {
m.errCount += 1
}
func (m *MockTestingT) FailNow() {
m.errCount += 1
}
func (m *MockTestingT) RequireFailed(t require.TestingT) {
require.Greater(t, m.errCount, 0, "Should have tracked a failure")
}
func (m *MockTestingT) RequireNoFailure(t require.TestingT) {
require.Equal(t, m.errCount, 0, "Should not have tracked a failure")
}
package testutil
import (
"math/rand"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
type StateMutatorMultiThreaded struct {
state *multithreaded.State
}
var _ testutil.StateMutator = (*StateMutatorMultiThreaded)(nil)
func NewStateMutatorMultiThreaded(state *multithreaded.State) testutil.StateMutator {
return &StateMutatorMultiThreaded{state: state}
}
func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) {
r := rand.New(rand.NewSource(randSeed))
step := testutil.RandStep(r)
m.state.PreimageKey = testutil.RandHash(r)
m.state.PreimageOffset = r.Uint32()
m.state.Heap = r.Uint32()
m.state.Step = step
m.state.LastHint = testutil.RandHint(r)
m.state.StepsSinceLastContextSwitch = uint64(r.Intn(exec.SchedQuantum))
// Randomize threads
activeStackThreads := r.Intn(2) + 1
inactiveStackThreads := r.Intn(3)
traverseRight := r.Intn(2) == 1
SetupThreads(randSeed+1, m.state, traverseRight, activeStackThreads, inactiveStackThreads)
}
func (m *StateMutatorMultiThreaded) SetHI(val uint32) {
m.state.GetCurrentThread().Cpu.HI = val
}
func (m *StateMutatorMultiThreaded) SetLO(val uint32) {
m.state.GetCurrentThread().Cpu.LO = val
}
func (m *StateMutatorMultiThreaded) SetExitCode(val uint8) {
m.state.ExitCode = val
}
func (m *StateMutatorMultiThreaded) SetExited(val bool) {
m.state.Exited = val
}
func (m *StateMutatorMultiThreaded) SetPC(val uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.PC = val
}
func (m *StateMutatorMultiThreaded) SetHeap(val uint32) {
m.state.Heap = val
}
func (m *StateMutatorMultiThreaded) SetNextPC(val uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.NextPC = val
}
func (m *StateMutatorMultiThreaded) SetLastHint(val hexutil.Bytes) {
m.state.LastHint = val
}
func (m *StateMutatorMultiThreaded) SetPreimageKey(val common.Hash) {
m.state.PreimageKey = val
}
func (m *StateMutatorMultiThreaded) SetPreimageOffset(val uint32) {
m.state.PreimageOffset = val
}
func (m *StateMutatorMultiThreaded) SetStep(val uint64) {
m.state.Step = val
}
package testutil
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"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/testutil"
)
type StateMutatorMultiThreaded struct {
state *multithreaded.State
func GetMtState(t require.TestingT, vm mipsevm.FPVM) *multithreaded.State {
state := vm.GetState()
mtState, ok := state.(*multithreaded.State)
if !ok {
require.Fail(t, "Failed to cast FPVMState to multithreaded State type")
}
return mtState
}
var _ testutil.StateMutator = (*StateMutatorMultiThreaded)(nil)
func NewStateMutatorMultiThreaded(state *multithreaded.State) testutil.StateMutator {
return &StateMutatorMultiThreaded{state: state}
}
func (m *StateMutatorMultiThreaded) SetHI(val uint32) {
m.state.GetCurrentThread().Cpu.HI = val
}
func (m *StateMutatorMultiThreaded) SetLO(val uint32) {
m.state.GetCurrentThread().Cpu.LO = val
}
func (m *StateMutatorMultiThreaded) SetExitCode(val uint8) {
m.state.ExitCode = val
}
func (m *StateMutatorMultiThreaded) SetExited(val bool) {
m.state.Exited = val
}
func (m *StateMutatorMultiThreaded) SetPC(val uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.PC = val
}
func (m *StateMutatorMultiThreaded) SetHeap(val uint32) {
m.state.Heap = val
}
func (m *StateMutatorMultiThreaded) SetNextPC(val uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.NextPC = val
}
func (m *StateMutatorMultiThreaded) SetLastHint(val hexutil.Bytes) {
m.state.LastHint = val
}
func (m *StateMutatorMultiThreaded) SetPreimageKey(val common.Hash) {
m.state.PreimageKey = val
}
func (m *StateMutatorMultiThreaded) SetPreimageOffset(val uint32) {
m.state.PreimageOffset = val
}
func (m *StateMutatorMultiThreaded) SetStep(val uint64) {
m.state.Step = val
}
func (m *StateMutatorMultiThreaded) GetRegistersRef() *[32]uint32 {
return m.state.GetRegistersRef()
func RandomState(seed int) *multithreaded.State {
state := multithreaded.CreateEmptyState()
mut := StateMutatorMultiThreaded{state}
mut.Randomize(int64(seed))
return state
}
package testutil
import (
"math/rand"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
func RandomThread(randSeed int64) *multithreaded.ThreadState {
r := rand.New(rand.NewSource(randSeed))
thread := multithreaded.CreateEmptyThread()
pc := testutil.RandPC(r)
thread.Registers = *testutil.RandRegisters(r)
thread.Cpu.PC = pc
thread.Cpu.NextPC = pc + 4
thread.Cpu.HI = r.Uint32()
thread.Cpu.LO = r.Uint32()
return thread
}
func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRight bool) {
singleThread := RandomThread(int64(randSeed))
state.NextThreadId = singleThread.ThreadId + 1
state.TraverseRight = traverseRight
if traverseRight {
state.RightThreadStack = []*multithreaded.ThreadState{singleThread}
state.LeftThreadStack = []*multithreaded.ThreadState{}
} else {
state.RightThreadStack = []*multithreaded.ThreadState{}
state.LeftThreadStack = []*multithreaded.ThreadState{singleThread}
}
}
func SetupThreads(randomSeed int64, state *multithreaded.State, traverseRight bool, activeStackSize, otherStackSize int) {
var activeStack, otherStack []*multithreaded.ThreadState
tid := uint32(0)
for i := 0; i < activeStackSize; i++ {
thread := RandomThread(randomSeed + int64(i))
thread.ThreadId = tid
activeStack = append(activeStack, thread)
tid++
}
for i := 0; i < otherStackSize; i++ {
thread := RandomThread(randomSeed + int64(i+activeStackSize))
thread.ThreadId = tid
otherStack = append(otherStack, thread)
tid++
}
state.NextThreadId = tid
state.TraverseRight = traverseRight
if traverseRight {
state.RightThreadStack = activeStack
state.LeftThreadStack = otherStack
} else {
state.LeftThreadStack = activeStack
state.RightThreadStack = otherStack
}
}
type ThreadIterator struct {
left []*multithreaded.ThreadState
right []*multithreaded.ThreadState
traverseRight bool
}
func NewThreadIterator(state *multithreaded.State) ThreadIterator {
return ThreadIterator{
left: state.LeftThreadStack,
right: state.RightThreadStack,
traverseRight: state.TraverseRight,
}
}
func (i *ThreadIterator) currentThread() *multithreaded.ThreadState {
var currentThread *multithreaded.ThreadState
if i.traverseRight {
currentThread = i.right[len(i.right)-1]
} else {
currentThread = i.left[len(i.left)-1]
}
return currentThread
}
func (i *ThreadIterator) Next() *multithreaded.ThreadState {
rightLen := len(i.right)
leftLen := len(i.left)
activeThread := i.currentThread()
if i.traverseRight {
i.right = i.right[:rightLen-1]
i.left = append(i.left, activeThread)
i.traverseRight = len(i.right) > 0
} else {
i.left = i.left[:leftLen-1]
i.right = append(i.right, activeThread)
i.traverseRight = len(i.left) == 0
}
return i.currentThread()
}
// FindNextThread Finds the next thread in line according to thread traversal logic
func FindNextThread(state *multithreaded.State) *multithreaded.ThreadState {
it := NewThreadIterator(state)
return it.Next()
}
type ThreadFilter func(thread *multithreaded.ThreadState) bool
func FindNextThreadFiltered(state *multithreaded.State, filter ThreadFilter) *multithreaded.ThreadState {
it := NewThreadIterator(state)
// Worst case - walk all the way left, then all the way back right
// Example w 3 threads: 1,2,3,3,2,1,0 -> 7 steps to find thread 0
maxIterations := state.ThreadCount()*2 + 1
for i := 0; i < maxIterations; i++ {
next := it.Next()
if filter(next) {
return next
}
}
return nil
}
func FindNextThreadExcluding(state *multithreaded.State, threadId uint32) *multithreaded.ThreadState {
return FindNextThreadFiltered(state, func(t *multithreaded.ThreadState) bool {
return t.ThreadId != threadId
})
}
func FindThread(state *multithreaded.State, threadId uint32) *multithreaded.ThreadState {
for _, t := range GetAllThreads(state) {
if t.ThreadId == threadId {
return t
}
}
return nil
}
func GetAllThreads(state *multithreaded.State) []*multithreaded.ThreadState {
allThreads := make([]*multithreaded.ThreadState, 0, state.ThreadCount())
allThreads = append(allThreads, state.RightThreadStack[:]...)
allThreads = append(allThreads, state.LeftThreadStack[:]...)
return allThreads
}
func GetThreadStacks(state *multithreaded.State) (activeStack, inactiveStack []*multithreaded.ThreadState) {
if state.TraverseRight {
activeStack = state.RightThreadStack
inactiveStack = state.LeftThreadStack
} else {
activeStack = state.LeftThreadStack
inactiveStack = state.RightThreadStack
}
return activeStack, inactiveStack
}
package testutil
import (
"math/rand"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -12,6 +14,24 @@ type StateMutatorSingleThreaded struct {
state *singlethreaded.State
}
func (m *StateMutatorSingleThreaded) Randomize(randSeed int64) {
r := rand.New(rand.NewSource(randSeed))
pc := testutil.RandPC(r)
step := testutil.RandStep(r)
m.state.PreimageKey = testutil.RandHash(r)
m.state.PreimageOffset = r.Uint32()
m.state.Cpu.PC = pc
m.state.Cpu.NextPC = pc + 4
m.state.Cpu.HI = r.Uint32()
m.state.Cpu.LO = r.Uint32()
m.state.Heap = r.Uint32()
m.state.Step = step
m.state.LastHint = testutil.RandHint(r)
m.state.Registers = *testutil.RandRegisters(r)
}
var _ testutil.StateMutator = (*StateMutatorSingleThreaded)(nil)
func NewStateMutatorSingleThreaded(state *singlethreaded.State) testutil.StateMutator {
......@@ -61,7 +81,3 @@ func (m *StateMutatorSingleThreaded) SetPreimageOffset(val uint32) {
func (m *StateMutatorSingleThreaded) SetStep(val uint64) {
m.state.Step = val
}
func (m *StateMutatorSingleThreaded) GetRegistersRef() *[32]uint32 {
return m.state.GetRegistersRef()
}
......@@ -10,13 +10,10 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
......@@ -143,10 +140,10 @@ func TestEVMSingleStep(t *testing.T) {
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(tt.pc), testutil.WithNextPC(tt.nextPC))
state := goVm.GetState()
state.GetMemory().SetMemory(tt.pc, tt.insn)
curStep := state.GetStep()
step := state.GetStep()
// Setup expectations
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = tt.expectNextPC
......@@ -159,15 +156,7 @@ func TestEVMSingleStep(t *testing.T) {
// Check expectations
expected.Validate(t, state)
evm := testutil.NewMIPSEVM(v.Contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
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")
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
})
}
}
......@@ -209,7 +198,7 @@ func TestEVM_MMap(t *testing.T) {
state.GetRegistersRef()[5] = c.size
step := state.GetStep()
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......@@ -232,15 +221,7 @@ func TestEVM_MMap(t *testing.T) {
// Check expectations
expected.Validate(t, state)
evm := testutil.NewMIPSEVM(v.Contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
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, tracer)
})
}
}
......@@ -420,9 +401,9 @@ func TestEVMSysWriteHint(t *testing.T) {
err := state.GetMemory().SetMemoryRange(uint32(tt.memOffset), bytes.NewReader(tt.hintData))
require.NoError(t, err)
state.GetMemory().SetMemory(state.GetPC(), insn)
curStep := state.GetStep()
step := state.GetStep()
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......@@ -435,15 +416,7 @@ func TestEVMSysWriteHint(t *testing.T) {
expected.Validate(t, state)
require.Equal(t, tt.expectedHints, oracle.Hints())
evm := testutil.NewMIPSEVM(v.Contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
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")
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
})
}
}
......@@ -451,7 +424,6 @@ func TestEVMSysWriteHint(t *testing.T) {
func TestEVMFault(t *testing.T) {
var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer
sender := common.Address{0x13, 0x37}
versions := GetMipsVersionTestCases(t)
cases := []struct {
......@@ -468,9 +440,6 @@ 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) {
env, evmState := testutil.NewEVMEnv(v.Contracts)
env.Config.Tracer = tracer
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithNextPC(tt.nextPC))
state := goVm.GetState()
state.GetMemory().SetMemory(0, tt.insn)
......@@ -478,20 +447,7 @@ func TestEVMFault(t *testing.T) {
state.GetRegistersRef()[31] = testutil.EndAddr
require.Panics(t, func() { _, _ = goVm.Step(true) })
insnProof := state.GetMemory().MerkleProof(0)
encodedWitness, _ := state.EncodeWitness()
stepWitness := &mipsevm.StepWitness{
State: encodedWitness,
ProofData: insnProof[:],
}
input := testutil.EncodeStepInput(t, stepWitness, mipsevm.LocalContext{}, v.Contracts.Artifacts.MIPS)
startingGas := uint64(30_000_000)
_, _, err := env.Call(vm.AccountRef(sender), v.Contracts.Addresses.MIPS, input, startingGas, common.U2560)
require.EqualValues(t, err, vm.ErrExecutionReverted)
logs := evmState.Logs()
require.Equal(t, 0, len(logs))
testutil.AssertEVMReverts(t, state, v.Contracts, tracer)
})
}
}
......@@ -514,7 +470,7 @@ func TestHelloEVM(t *testing.T) {
start := time.Now()
for i := 0; i < 400_000; i++ {
curStep := goVm.GetState().GetStep()
step := goVm.GetState().GetStep()
if goVm.GetState().GetExited() {
break
}
......@@ -525,7 +481,7 @@ func TestHelloEVM(t *testing.T) {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn)
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()
......
package tests
import (
"fmt"
"os"
"slices"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/stretchr/testify/require"
"golang.org/x/exp/maps"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"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"
)
func TestEVM_CloneFlags(t *testing.T) {
func TestEVM_SysClone_FlagHandling(t *testing.T) {
contracts := testutil.TestContractsSetup(t, testutil.MipsMultithreaded)
var tracer *tracing.Hooks
......@@ -34,27 +38,32 @@ func TestEVM_CloneFlags(t *testing.T) {
{"multiple unsupported flags", exec.CloneUntraced | exec.CloneParentSettid, false},
}
const insn = uint32(0x00_00_00_0C) // syscall instruction
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
state := multithreaded.CreateEmptyState()
state.Memory.SetMemory(state.GetPC(), insn)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysClone // Set syscall number
state.GetRegistersRef()[4] = tt.flags // Set first argument
state.GetRegistersRef()[4] = c.flags // Set first argument
curStep := state.Step
var err error
var stepWitness *mipsevm.StepWitness
us := multithreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
if !tt.valid {
if !c.valid {
// The VM should exit
stepWitness, err = us.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, 1, state.ThreadCount())
} else {
stepWitness, err = us.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, 2, state.ThreadCount())
}
evm := testutil.NewMIPSEVM(contracts)
......@@ -68,3 +77,886 @@ func TestEVM_CloneFlags(t *testing.T) {
})
}
}
func TestEVM_SysClone_Successful(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
traverseRight bool
}{
{"traverse left", false},
{"traverse right", true},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
stackPtr := uint32(100)
goVm, state, contracts := setup(t, i)
mttestutil.InitializeSingleThread(i*333, state, c.traverseRight)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.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, uint32(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[2] = 0
expectedNewThread.Registers[7] = 0
expectedNewThread.Registers[29] = stackPtr
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, tracer)
})
}
}
func TestEVM_SysGetTID(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
threadId uint32
}{
{"zero", 0},
{"non-zero", 11},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*789)
mttestutil.InitializeSingleThread(i*789, state, false)
state.GetCurrentThread().ThreadId = c.threadId
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.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
// 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, tracer)
})
}
}
func TestEVM_SysExit(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
threadCount int
shouldExitGlobally bool
}{
// If we exit the last thread, the whole process should exit
{name: "one thread", threadCount: 1, shouldExitGlobally: true},
{name: "two threads ", threadCount: 2},
{name: "three threads ", threadCount: 3},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
exitCode := uint8(3)
goVm, state, contracts := setup(t, i*133)
mttestutil.SetupThreads(int64(i*1111), state, i%2 == 0, c.threadCount, 0)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysExit // Set syscall number
state.GetRegistersRef()[4] = uint32(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
}
// 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, tracer)
})
}
}
func TestEVM_PopExitedThread(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
traverseRight bool
activeStackThreadCount int
expectTraverseRightPostState bool
}{
{name: "traverse right", traverseRight: true, activeStackThreadCount: 2, expectTraverseRightPostState: true},
{name: "traverse right, switch directions", traverseRight: true, activeStackThreadCount: 1, expectTraverseRightPostState: false},
{name: "traverse left", traverseRight: false, activeStackThreadCount: 2, expectTraverseRightPostState: false},
{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)
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
// 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)
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
address uint32
targetValue uint32
actualValue uint32
timeout uint32
shouldFail bool
shouldSetTimeout bool
}{
{name: "successful wait, no timeout", address: 0x1234, targetValue: 0x01, actualValue: 0x01},
{name: "memory mismatch, no timeout", address: 0x1200, targetValue: 0x01, actualValue: 0x02, shouldFail: true},
{name: "successful wait w timeout", address: 0x1234, targetValue: 0x01, actualValue: 0x01, timeout: 1000000, shouldSetTimeout: true},
{name: "memory mismatch w timeout", address: 0x1200, targetValue: 0x01, actualValue: 0x02, timeout: 2000000, shouldFail: true},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*1234)
step := state.GetStep()
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.Memory.SetMemory(c.address, c.actualValue)
state.GetRegistersRef()[2] = exec.SysFutex // Set syscall number
state.GetRegistersRef()[4] = c.address
state.GetRegistersRef()[5] = exec.FutexWaitPrivate
state.GetRegistersRef()[6] = c.targetValue
state.GetRegistersRef()[7] = c.timeout
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
expected.StepsSinceLastContextSwitch += 1
if c.shouldFail {
expected.ActiveThread().PC = state.GetCpu().NextPC
expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4
expected.ActiveThread().Registers[2] = exec.SysErrorSignal
expected.ActiveThread().Registers[7] = exec.MipsEAGAIN
} else {
// PC and return registers should not update on success, updates happen when wait completes
expected.ActiveThread().FutexAddr = c.address
expected.ActiveThread().FutexVal = c.targetValue
expected.ActiveThread().FutexTimeoutStep = exec.FutexNoTimeout
if c.shouldSetTimeout {
expected.ActiveThread().FutexTimeoutStep = step + exec.FutexTimeoutSteps + 1
}
}
// 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, tracer)
})
}
}
func TestEVM_SysFutex_WakePrivate(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
address uint32
activeThreadCount int
inactiveThreadCount int
traverseRight bool
expectTraverseRight bool
}{
{name: "Traverse right", address: 0x6789, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true},
{name: "Traverse right, no left threads", address: 0x6789, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true},
{name: "Traverse right, single thread", address: 0x6789, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true},
{name: "Traverse left", address: 0x6789, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false},
{name: "Traverse left, switch directions", address: 0x6789, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true},
{name: "Traverse left, single thread", address: 0x6789, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*1122)
mttestutil.SetupThreads(int64(i*2244), state, c.traverseRight, c.activeThreadCount, c.inactiveThreadCount)
step := state.Step
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysFutex // Set syscall number
state.GetRegistersRef()[4] = c.address
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.Wakeup = c.address
expected.ExpectPreemption(state)
expected.TraverseRight = c.expectTraverseRight
if c.traverseRight != c.expectTraverseRight {
// If we preempt the current thread and then switch directions, the same
// thread will remain active
expected.ActiveThreadId = state.GetCurrentThread().ThreadId
}
// 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, tracer)
})
}
}
func TestEVM_SysFutex_UnsupportedOp(t *testing.T) {
var tracer *tracing.Hooks
// From: https://github.com/torvalds/linux/blob/5be63fc19fcaa4c236b307420483578a56986a37/include/uapi/linux/futex.h
const FUTEX_PRIVATE_FLAG = 128
const FUTEX_WAIT = 0
const FUTEX_WAKE = 1
const FUTEX_FD = 2
const FUTEX_REQUEUE = 3
const FUTEX_CMP_REQUEUE = 4
const FUTEX_WAKE_OP = 5
const FUTEX_LOCK_PI = 6
const FUTEX_UNLOCK_PI = 7
const FUTEX_TRYLOCK_PI = 8
const FUTEX_WAIT_BITSET = 9
const FUTEX_WAKE_BITSET = 10
const FUTEX_WAIT_REQUEUE_PI = 11
const FUTEX_CMP_REQUEUE_PI = 12
const FUTEX_LOCK_PI2 = 13
unsupportedFutexOps := map[string]uint32{
"FUTEX_WAIT": FUTEX_WAIT,
"FUTEX_WAKE": FUTEX_WAKE,
"FUTEX_FD": FUTEX_FD,
"FUTEX_REQUEUE": FUTEX_REQUEUE,
"FUTEX_CMP_REQUEUE": FUTEX_CMP_REQUEUE,
"FUTEX_WAKE_OP": FUTEX_WAKE_OP,
"FUTEX_LOCK_PI": FUTEX_LOCK_PI,
"FUTEX_UNLOCK_PI": FUTEX_UNLOCK_PI,
"FUTEX_TRYLOCK_PI": FUTEX_TRYLOCK_PI,
"FUTEX_WAIT_BITSET": FUTEX_WAIT_BITSET,
"FUTEX_WAKE_BITSET": FUTEX_WAKE_BITSET,
"FUTEX_WAIT_REQUEUE_PI": FUTEX_WAIT_REQUEUE_PI,
"FUTEX_CMP_REQUEUE_PI": FUTEX_CMP_REQUEUE_PI,
"FUTEX_LOCK_PI2": FUTEX_LOCK_PI2,
"FUTEX_REQUEUE_PRIVATE": (FUTEX_REQUEUE | FUTEX_PRIVATE_FLAG),
"FUTEX_CMP_REQUEUE_PRIVATE": (FUTEX_CMP_REQUEUE | FUTEX_PRIVATE_FLAG),
"FUTEX_WAKE_OP_PRIVATE": (FUTEX_WAKE_OP | FUTEX_PRIVATE_FLAG),
"FUTEX_LOCK_PI_PRIVATE": (FUTEX_LOCK_PI | FUTEX_PRIVATE_FLAG),
"FUTEX_LOCK_PI2_PRIVATE": (FUTEX_LOCK_PI2 | FUTEX_PRIVATE_FLAG),
"FUTEX_UNLOCK_PI_PRIVATE": (FUTEX_UNLOCK_PI | FUTEX_PRIVATE_FLAG),
"FUTEX_TRYLOCK_PI_PRIVATE": (FUTEX_TRYLOCK_PI | FUTEX_PRIVATE_FLAG),
"FUTEX_WAIT_BITSET_PRIVATE": (FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG),
"FUTEX_WAKE_BITSET_PRIVATE": (FUTEX_WAKE_BITSET | FUTEX_PRIVATE_FLAG),
"FUTEX_WAIT_REQUEUE_PI_PRIVATE": (FUTEX_WAIT_REQUEUE_PI | FUTEX_PRIVATE_FLAG),
"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))
step := state.GetStep()
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.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
// 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, tracer)
})
}
}
func TestEVM_SysYield(t *testing.T) {
runPreemptSyscall(t, "SysSchedYield", exec.SysSchedYield)
}
func TestEVM_SysNanosleep(t *testing.T) {
runPreemptSyscall(t, "SysNanosleep", exec.SysNanosleep)
}
func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) {
var tracer *tracing.Hooks
cases := []struct {
name string
traverseRight bool
activeThreads int
inactiveThreads int
}{
{name: "Last active thread", activeThreads: 1, inactiveThreads: 2},
{name: "Only thread", activeThreads: 1, inactiveThreads: 0},
{name: "Do not change directions", activeThreads: 2, inactiveThreads: 2},
{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)
mttestutil.SetupThreads(int64(i*3259), state, traverseRight, c.activeThreads, c.inactiveThreads)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = 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
// 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, tracer)
})
}
}
}
func TestEVM_SysOpen(t *testing.T) {
var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 5512)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.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, tracer)
}
var NoopSyscalls = map[string]uint32{
"SysGetAffinity": 4240,
"SysMadvise": 4218,
"SysRtSigprocmask": 4195,
"SysSigaltstack": 4206,
"SysRtSigaction": 4194,
"SysPrlimit64": 4338,
"SysClose": 4006,
"SysPread64": 4200,
"SysFstat64": 4215,
"SysOpenAt": 4288,
"SysReadlink": 4085,
"SysReadlinkAt": 4298,
"SysIoctl": 4054,
"SysEpollCreate1": 4326,
"SysPipe2": 4328,
"SysEpollCtl": 4249,
"SysEpollPwait": 4313,
"SysGetRandom": 4353,
"SysUname": 4122,
"SysStat64": 4213,
"SysGetuid": 4024,
"SysGetgid": 4047,
"SysLlseek": 4140,
"SysMinCore": 4217,
"SysTgkill": 4266,
"SysMunmap": 4091,
"SysSetITimer": 4104,
"SysTimerCreate": 4257,
"SysTimerSetTime": 4258,
"SysTimerDelete": 4261,
"SysClockGetTime": 4263,
}
func TestEVM_NoopSyscall(t *testing.T) {
var tracer *tracing.Hooks
for noopName, noopVal := range NoopSyscalls {
t.Run(noopName, func(t *testing.T) {
goVm, state, contracts := setup(t, int(noopVal))
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = noopVal // 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, tracer)
})
}
}
func TestEVM_UnsupportedSyscall(t *testing.T) {
var tracer *tracing.Hooks
var NoopSyscallNums = maps.Values(NoopSyscalls)
var SupportedSyscalls = []uint32{exec.SysMmap, exec.SysBrk, exec.SysClone, exec.SysExitGroup, exec.SysRead, exec.SysWrite, exec.SysFcntl, exec.SysExit, exec.SysSchedYield, exec.SysGetTID, exec.SysFutex, exec.SysOpen, exec.SysNanosleep}
unsupportedSyscalls := make([]uint32, 0, 400)
for i := 4000; i < 4400; i++ {
candidate := uint32(i)
if slices.Contains(SupportedSyscalls, candidate) || slices.Contains(NoopSyscallNums, candidate) {
continue
}
unsupportedSyscalls = append(unsupportedSyscalls, candidate)
}
for i, syscallNum := range unsupportedSyscalls {
testName := fmt.Sprintf("Unsupported syscallNum %v", syscallNum)
t.Run(testName, func(t *testing.T) {
goVm, state, contracts := setup(t, i*3434)
// Setup basic getThreadId syscall instruction
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = syscallNum
// Set up post-state expectations
require.Panics(t, func() { _, _ = goVm.Step(true) })
testutil.AssertEVMReverts(t, state, contracts, tracer)
})
}
}
func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
step uint64
activeStackSize int
otherStackSize int
futexAddr uint32
targetValue uint32
actualValue uint32
timeoutStep uint64
shouldWakeup bool
shouldTimeout bool
}{
{name: "Preempt, no timeout #1", step: 100, activeStackSize: 1, otherStackSize: 0, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, no timeout #2", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, no timeout #3", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, with timeout #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 101},
{name: "Preempt, with timeout #2", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 150},
{name: "Wakeup, no timeout #1", step: 100, activeStackSize: 1, otherStackSize: 0, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup, no timeout #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup with timeout #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: 100, shouldWakeup: true, shouldTimeout: true},
{name: "Wakeup with timeout #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x02, actualValue: 0x02, timeoutStep: 100, shouldWakeup: true, shouldTimeout: true},
{name: "Wakeup with timeout #3", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x02, actualValue: 0x02, timeoutStep: 50, shouldWakeup: true, shouldTimeout: true},
}
for _, c := range cases {
for i, traverseRight := range []bool{true, false} {
testName := fmt.Sprintf("%v (traverseRight=%v)", c.name, traverseRight)
t.Run(testName, func(t *testing.T) {
// Sanity check
if !c.shouldWakeup && c.shouldTimeout {
require.Fail(t, "Invalid test case - cannot expect a timeout with no wakeup")
}
goVm, state, contracts := setup(t, i)
mttestutil.SetupThreads(int64(i*101), state, traverseRight, c.activeStackSize, c.otherStackSize)
state.Step = c.step
activeThread := state.GetCurrentThread()
activeThread.FutexAddr = c.futexAddr
activeThread.FutexVal = c.targetValue
activeThread.FutexTimeoutStep = c.timeoutStep
state.GetMemory().SetMemory(c.futexAddr, c.actualValue)
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
if c.shouldWakeup {
expected.ActiveThread().FutexAddr = exec.FutexEmptyAddr
expected.ActiveThread().FutexVal = 0
expected.ActiveThread().FutexTimeoutStep = 0
// PC and return registers are updated onWaitComplete
expected.ActiveThread().PC = state.GetCpu().NextPC
expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4
if c.shouldTimeout {
expected.ActiveThread().Registers[2] = exec.SysErrorSignal
expected.ActiveThread().Registers[7] = exec.MipsETIMEDOUT
} else {
expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
}
} else {
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, c.step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_NormalTraversal_Full(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
threadCount int
}{
{"1 thread", 1},
{"2 threads", 2},
{"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)
mttestutil.SetupThreads(int64(i*2947), state, traverseRight, c.threadCount, 0)
// Put threads into a waiting state so that we just traverse through them
for _, thread := range mttestutil.GetAllThreads(state) {
thread.FutexAddr = 0x04
thread.FutexTimeoutStep = exec.FutexNoTimeout
}
step := state.Step
initialState := mttestutil.NewExpectedMTState(state)
// Loop through all the threads to get back to the starting state
iterations := c.threadCount * 2
for i := 0; i < iterations; i++ {
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
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, tracer)
}
// We should be back to the original state with only a few modifications
initialState.Step += uint64(iterations)
initialState.StepsSinceLastContextSwitch = 0
initialState.Validate(t, state)
})
}
}
}
func TestEVM_WakeupTraversalStep(t *testing.T) {
wakeupAddr := uint32(0x1234)
wakeupVal := uint32(0x999)
var tracer *tracing.Hooks
cases := []struct {
name string
futexAddr uint32
targetVal uint32
traverseRight bool
activeStackSize int
otherStackSize int
shouldClearWakeup bool
shouldPreempt bool
}{
{name: "Matching addr, not wakeable, first thread", futexAddr: wakeupAddr, targetVal: wakeupVal, traverseRight: false, activeStackSize: 3, otherStackSize: 0, shouldClearWakeup: true},
{name: "Matching addr, wakeable, first thread", futexAddr: wakeupAddr, targetVal: wakeupVal + 1, traverseRight: false, activeStackSize: 3, otherStackSize: 0, shouldClearWakeup: true},
{name: "Matching addr, not wakeable, last thread", futexAddr: wakeupAddr, targetVal: wakeupVal, traverseRight: true, activeStackSize: 1, otherStackSize: 2, shouldClearWakeup: true},
{name: "Matching addr, wakeable, last thread", futexAddr: wakeupAddr, targetVal: wakeupVal + 1, traverseRight: true, activeStackSize: 1, otherStackSize: 2, shouldClearWakeup: true},
{name: "Matching addr, not wakeable, intermediate thread", futexAddr: wakeupAddr, targetVal: wakeupVal, traverseRight: false, activeStackSize: 2, otherStackSize: 2, shouldClearWakeup: true},
{name: "Matching addr, wakeable, intermediate thread", futexAddr: wakeupAddr, targetVal: wakeupVal + 1, traverseRight: true, activeStackSize: 2, otherStackSize: 2, shouldClearWakeup: true},
{name: "Mismatched addr, last thread", futexAddr: wakeupAddr + 4, traverseRight: true, activeStackSize: 1, otherStackSize: 2, shouldPreempt: true, shouldClearWakeup: true},
{name: "Mismatched addr", futexAddr: wakeupAddr + 4, traverseRight: true, activeStackSize: 2, otherStackSize: 2, shouldPreempt: true},
{name: "Mismatched addr", futexAddr: wakeupAddr + 4, traverseRight: false, activeStackSize: 2, otherStackSize: 0, shouldPreempt: true},
{name: "Mismatched addr", futexAddr: wakeupAddr + 4, traverseRight: false, activeStackSize: 1, otherStackSize: 0, shouldPreempt: true},
{name: "Non-waiting thread", futexAddr: exec.FutexEmptyAddr, traverseRight: false, activeStackSize: 1, otherStackSize: 0, shouldPreempt: true},
{name: "Non-waiting thread", futexAddr: exec.FutexEmptyAddr, traverseRight: true, activeStackSize: 2, otherStackSize: 1, shouldPreempt: true},
{name: "Non-waiting thread, last thread", futexAddr: exec.FutexEmptyAddr, traverseRight: true, activeStackSize: 1, otherStackSize: 1, shouldPreempt: true, shouldClearWakeup: true},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*2000)
mttestutil.SetupThreads(int64(i*101), state, c.traverseRight, c.activeStackSize, c.otherStackSize)
step := state.Step
state.Wakeup = wakeupAddr
state.GetMemory().SetMemory(wakeupAddr, wakeupVal)
activeThread := state.GetCurrentThread()
activeThread.FutexAddr = c.futexAddr
activeThread.FutexVal = c.targetVal
activeThread.FutexTimeoutStep = exec.FutexNoTimeout
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
if c.shouldClearWakeup {
expected.Wakeup = exec.FutexEmptyAddr
}
if c.shouldPreempt {
// Just preempt the current thread
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, tracer)
})
}
}
func TestEVM_WakeupTraversal_Full(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
threadCount int
}{
{"1 thread", 1},
{"2 threads", 2},
{"3 threads", 3},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
// Setup
goVm, state, contracts := setup(t, i*789)
mttestutil.SetupThreads(int64(i*2947), state, false, c.threadCount, 0)
state.Wakeup = 0x08
step := state.Step
initialState := mttestutil.NewExpectedMTState(state)
// Loop through all the threads to get back to the starting state
iterations := c.threadCount * 2
for i := 0; i < iterations; i++ {
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
expected.ExpectPreemption(state)
// State transition
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
// We should clear the wakeup on the last step
if i == iterations-1 {
expected.Wakeup = exec.FutexEmptyAddr
}
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
}
// We should be back to the original state with only a few modifications
initialState.Step += uint64(iterations)
initialState.StepsSinceLastContextSwitch = 0
initialState.Wakeup = exec.FutexEmptyAddr
initialState.Validate(t, state)
})
}
}
func TestEVM_SchedQuantumThreshold(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
stepsSinceLastContextSwitch uint64
shouldPreempt bool
}{
{name: "just under threshold", stepsSinceLastContextSwitch: exec.SchedQuantum - 1},
{name: "at threshold", stepsSinceLastContextSwitch: exec.SchedQuantum, shouldPreempt: true},
{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)
// Setup basic getThreadId syscall instruction
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.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
}
// 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, tracer)
})
}
}
func setup(t require.TestingT, randomSeed int) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) {
v := GetMultiThreadedTestCase(t)
vm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(randomSeed)))
state := mttestutil.GetMtState(t, vm)
return vm, state, v.Contracts
}
......@@ -6,7 +6,6 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
......@@ -30,7 +29,7 @@ func FuzzStateSyscallBrk(f *testing.F) {
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
step := state.GetStep()
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......@@ -42,12 +41,7 @@ func FuzzStateSyscallBrk(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, nil)
})
}
})
......@@ -74,7 +68,7 @@ func FuzzStateSyscallMmap(f *testing.F) {
state.GetRegistersRef()[5] = siz
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......@@ -102,12 +96,7 @@ func FuzzStateSyscallMmap(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, nil)
})
}
})
......@@ -126,7 +115,7 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
step := state.GetStep()
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.Exited = true
expected.ExitCode = exitCode
......@@ -136,12 +125,7 @@ func FuzzStateSyscallExitGroup(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, nil)
})
}
})
......@@ -161,7 +145,7 @@ func FuzzStateSyscallFcntl(f *testing.F) {
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
step := state.GetStep()
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......@@ -187,12 +171,7 @@ func FuzzStateSyscallFcntl(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, nil)
})
}
})
......@@ -217,7 +196,7 @@ func FuzzStateHintRead(f *testing.F) {
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
step := state.GetStep()
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......@@ -229,12 +208,7 @@ func FuzzStateHintRead(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, nil)
})
}
})
......@@ -273,7 +247,7 @@ func FuzzStatePreimageRead(f *testing.F) {
writeLen = preimageDataLen - preimageOffset
}
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......@@ -292,12 +266,7 @@ func FuzzStatePreimageRead(f *testing.F) {
// modify memory - it's possible we just write the leading zero bytes of the length prefix
require.Equal(t, expected.MemoryRoot, common.Hash(state.GetMemory().MerkleRoot()))
}
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, nil)
})
}
})
......@@ -329,7 +298,7 @@ func FuzzStateHintWrite(f *testing.F) {
// Set instruction
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......@@ -342,12 +311,7 @@ func FuzzStateHintWrite(f *testing.F) {
// TODO(cp-983) - validate expected hints
expected.Validate(t, state, testutil.SkipHintValidation)
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, nil)
})
}
})
......@@ -377,7 +341,7 @@ func FuzzStatePreimageWrite(f *testing.F) {
count = sz
}
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......@@ -391,12 +355,7 @@ func FuzzStatePreimageWrite(f *testing.F) {
// TODO(cp-983) - validate preimage key
expected.Validate(t, state, testutil.SkipPreimageKeyValidation)
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, nil)
})
}
})
......
......@@ -4,44 +4,63 @@ import (
"os"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"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"
)
// TODO
func FuzzStateSyscallCloneMT(f *testing.F) {
v := GetMultiThreadedTestCase(f)
// t.Skip is causing linting check to fail, disable for now
//nolint:staticcheck
f.Fuzz(func(t *testing.T, seed int64) {
// TODO(cp-903) Customize test for multi-threaded vm
t.Skip("TODO - customize this test for MTCannon")
f.Fuzz(func(t *testing.T, nextThreadId, stackPtr uint32, seed int64) {
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysClone
state := mttestutil.GetMtState(t, goVm)
// Update existing threads to avoid collision with nextThreadId
if mttestutil.FindThread(state, nextThreadId) != nil {
for i, t := range mttestutil.GetAllThreads(state) {
t.ThreadId = nextThreadId - uint32(i+1)
}
}
// Setup
state.NextThreadId = nextThreadId
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysClone
state.GetRegistersRef()[4] = exec.ValidCloneFlags
state.GetRegistersRef()[5] = stackPtr
step := state.GetStep()
expected := testutil.CreateExpectedState(state)
// Set up expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
expected.Registers[2] = 0x1
expected.Registers[7] = 0
// Set original thread expectations
expected.PrestateActiveThread().PC = state.GetCpu().NextPC
expected.PrestateActiveThread().NextPC = state.GetCpu().NextPC + 4
expected.PrestateActiveThread().Registers[2] = nextThreadId
expected.PrestateActiveThread().Registers[7] = 0
// Set expectations for new, cloned thread
expected.ActiveThreadId = nextThreadId
epxectedNewThread := expected.ExpectNewThread()
epxectedNewThread.PC = state.GetCpu().NextPC
epxectedNewThread.NextPC = state.GetCpu().NextPC + 4
epxectedNewThread.Registers[2] = 0
epxectedNewThread.Registers[7] = 0
epxectedNewThread.Registers[29] = stackPtr
expected.NextThreadId = nextThreadId + 1
expected.StepsSinceLastContextSwitch = 0
if state.TraverseRight {
expected.RightStackSize += 1
} else {
expected.LeftStackSize += 1
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
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, multithreaded.GetStateHashFn(), v.Contracts, nil)
})
}
......@@ -20,7 +20,7 @@ func FuzzStateSyscallCloneST(f *testing.F) {
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
step := state.GetStep()
expected := testutil.CreateExpectedState(state)
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
......
......@@ -9,6 +9,7 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/vm"
......@@ -151,3 +152,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, tracer *tracing.Hooks) {
evm := NewMIPSEVM(contracts)
evm.SetTracer(tracer)
LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, step, hashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
// AssertEVMReverts runs a single evm step from an FPVM prestate and asserts that the VM panics
func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *ContractMetadata, tracer *tracing.Hooks) {
insnProof := state.GetMemory().MerkleProof(state.GetPC())
encodedWitness, _ := state.EncodeWitness()
stepWitness := &mipsevm.StepWitness{
State: encodedWitness,
ProofData: insnProof[:],
}
input := EncodeStepInput(t, stepWitness, mipsevm.LocalContext{}, contracts.Artifacts.MIPS)
startingGas := uint64(30_000_000)
env, evmState := NewEVMEnv(contracts)
env.Config.Tracer = tracer
sender := common.Address{0x13, 0x37}
_, _, err := env.Call(vm.AccountRef(sender), contracts.Addresses.MIPS, input, startingGas, common.U2560)
require.EqualValues(t, err, vm.ErrExecutionReverted)
logs := evmState.Logs()
require.Equal(t, 0, len(logs))
}
package testutil
import (
"math/rand"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func RandHash(r *rand.Rand) common.Hash {
var bytes [32]byte
_, err := r.Read(bytes[:])
if err != nil {
panic(err)
}
return bytes
}
func RandHint(r *rand.Rand) []byte {
count := r.Intn(10)
bytes := make([]byte, count)
_, err := r.Read(bytes[:])
if err != nil {
panic(err)
}
return bytes
}
func RandRegisters(r *rand.Rand) *[32]uint32 {
registers := new([32]uint32)
for i := 0; i < 32; i++ {
registers[i] = r.Uint32()
}
return registers
}
func RandomBytes(t require.TestingT, seed int64, length uint32) []byte {
r := rand.New(rand.NewSource(seed))
randBytes := make([]byte, length)
if _, err := r.Read(randBytes); err != nil {
require.NoError(t, err)
}
return randBytes
}
func RandPC(r *rand.Rand) uint32 {
return AlignPC(r.Uint32())
}
func RandStep(r *rand.Rand) uint64 {
return BoundStep(r.Uint64())
}
......@@ -2,7 +2,6 @@ package testutil
import (
"fmt"
"math/rand"
"slices"
"testing"
......@@ -31,7 +30,7 @@ type StateMutator interface {
SetExited(val bool)
SetStep(val uint64)
SetLastHint(val hexutil.Bytes)
GetRegistersRef() *[32]uint32
Randomize(randSeed int64)
}
type StateOption func(state StateMutator)
......@@ -80,36 +79,26 @@ func WithStep(step uint64) StateOption {
func WithRandomization(seed int64) StateOption {
return func(mut StateMutator) {
RandomizeState(seed, mut)
mut.Randomize(seed)
}
}
func RandomizeState(seed int64, mut StateMutator) {
r := rand.New(rand.NewSource(seed))
func AlignPC(pc uint32) uint32 {
// Memory-align random pc and leave room for nextPC
pc := r.Uint32() & 0xFF_FF_FF_FC // Align address
pc = pc & 0xFF_FF_FF_FC // Align address
if pc >= 0xFF_FF_FF_FC {
// Leave room to set and then increment nextPC
pc = 0xFF_FF_FF_FC - 8
}
return pc
}
// Set random step, but leave room to increment
step := r.Uint64()
func BoundStep(step uint64) uint64 {
// Leave room to increment step at least once
if step == ^uint64(0) {
step -= 1
}
mut.SetPreimageKey(randHash(r))
mut.SetPreimageOffset(r.Uint32())
mut.SetPC(pc)
mut.SetNextPC(pc + 4)
mut.SetHI(r.Uint32())
mut.SetLO(r.Uint32())
mut.SetHeap(r.Uint32())
mut.SetStep(step)
mut.SetLastHint(randHint(r))
*mut.GetRegistersRef() = *randRegisters(r)
return step
}
type ExpectedState struct {
......@@ -128,7 +117,7 @@ type ExpectedState struct {
MemoryRoot common.Hash
}
func CreateExpectedState(fromState mipsevm.FPVMState) *ExpectedState {
func NewExpectedState(fromState mipsevm.FPVMState) *ExpectedState {
return &ExpectedState{
PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(),
......@@ -176,40 +165,3 @@ func (e *ExpectedState) Validate(t testing.TB, actualState mipsevm.FPVMState, fl
require.Equal(t, e.MemoryRoot, common.Hash(actualState.GetMemory().MerkleRoot()), fmt.Sprintf("Expect memory root = %v", e.MemoryRoot))
}
}
func randHash(r *rand.Rand) common.Hash {
var bytes [32]byte
_, err := r.Read(bytes[:])
if err != nil {
panic(err)
}
return bytes
}
func randHint(r *rand.Rand) []byte {
count := r.Intn(10)
bytes := make([]byte, count)
_, err := r.Read(bytes[:])
if err != nil {
panic(err)
}
return bytes
}
func randRegisters(r *rand.Rand) *[32]uint32 {
registers := new([32]uint32)
for i := 0; i < 32; i++ {
registers[i] = r.Uint32()
}
return registers
}
func RandomBytes(t require.TestingT, seed int64, length uint32) []byte {
r := rand.New(rand.NewSource(seed))
randBytes := make([]byte, length)
if _, err := r.Read(randBytes); err != nil {
require.NoError(t, err)
}
return randBytes
}
......@@ -148,8 +148,8 @@
"sourceCodeHash": "0xb6e219e8c2d81d75c48a1459907609e9096fe032a7447c88cd3e0d134752ac8e"
},
"src/cannon/MIPS2.sol": {
"initCodeHash": "0xbb425bd1c3cad13a77f5c9676b577606e2f8f320687739f529b257a042f58d85",
"sourceCodeHash": "0xe66f19942947f53ccd658b94c1ef6db39e947419d4ec7436067c6cc44452ff73"
"initCodeHash": "0x36b7c32cf9eba05e6db44910a25c800b801c075f8e053eca9515c6e0e4d8a902",
"sourceCodeHash": "0xa307c44a2d67bc84e75f4b7341345ed236da2e63c1f3f442416f14cd262126bf"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0xce7a1c3265e457a05d17b6d1a2ef93c4639caac3733c9cf88bfd192eae2c5788",
......
......@@ -51,8 +51,8 @@ contract MIPS2 is ISemver {
}
/// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.4
string public constant version = "1.0.0-beta.4";
/// @custom:semver 1.0.0-beta.5
string public constant version = "1.0.0-beta.5";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......@@ -202,7 +202,7 @@ contract MIPS2 is ISemver {
}
}
if (state.stepsSinceLastContextSwitch == sys.SCHED_QUANTUM) {
if (state.stepsSinceLastContextSwitch >= sys.SCHED_QUANTUM) {
preemptThread(state, thread);
return outputState();
}
......@@ -338,7 +338,6 @@ contract MIPS2 is ISemver {
} else if (syscall_no == sys.SYS_FUTEX) {
// args: a0 = addr, a1 = op, a2 = val, a3 = timeout
if (a1 == sys.FUTEX_WAIT_PRIVATE) {
thread.futexAddr = a0;
uint32 mem = MIPSMemory.readMem(
state.memRoot, a0 & 0xFFffFFfc, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1)
);
......@@ -346,6 +345,7 @@ contract MIPS2 is ISemver {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN;
} else {
thread.futexAddr = a0;
thread.futexVal = a2;
thread.futexTimeoutStep = a3 == 0 ? sys.FUTEX_NO_TIMEOUT : state.step + sys.FUTEX_TIMEOUT_STEPS;
// Leave cpu scalars as-is. This instruction will be completed by `onWaitComplete`
......@@ -381,9 +381,9 @@ contract MIPS2 is ISemver {
} else if (syscall_no == sys.SYS_OPEN) {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EBADF;
} else if (syscall_no == sys.SYS_CLOCK_GETTIME) {
} else if (syscall_no == sys.SYS_MUNMAP) {
// ignored
} else if (syscall_no == sys.SYS_GET_AFFINITY) {
} else if (syscall_no == sys.SYS_GETAFFINITY) {
// ignored
} else if (syscall_no == sys.SYS_MADVISE) {
// ignored
......@@ -443,8 +443,6 @@ contract MIPS2 is ISemver {
// ignored
} else if (syscall_no == sys.SYS_CLOCKGETTIME) {
// ignored
} else if (syscall_no == sys.SYS_MUNMAP) {
// ignored
} else {
revert("MIPS2: unimplemented syscall");
}
......
......@@ -42,8 +42,7 @@ library MIPSSyscalls {
uint32 internal constant SYS_OPEN = 4005;
uint32 internal constant SYS_NANOSLEEP = 4166;
// unused syscalls
uint32 internal constant SYS_CLOCK_GETTIME = 4263;
uint32 internal constant SYS_GET_AFFINITY = 4240;
uint32 internal constant SYS_MUNMAP = 4091;
uint32 internal constant SYS_GETAFFINITY = 4240;
uint32 internal constant SYS_MADVISE = 4218;
uint32 internal constant SYS_RTSIGPROCMASK = 4195;
......@@ -69,13 +68,13 @@ library MIPSSyscalls {
uint32 internal constant SYS_LLSEEK = 4140;
uint32 internal constant SYS_MINCORE = 4217;
uint32 internal constant SYS_TGKILL = 4266;
// profiling-related syscalls - ignored
uint32 internal constant SYS_SETITIMER = 4104;
uint32 internal constant SYS_TIMERCREATE = 4257;
uint32 internal constant SYS_TIMERSETTIME = 4258;
uint32 internal constant SYS_TIMERDELETE = 4261;
uint32 internal constant SYS_CLOCKGETTIME = 4263;
uint32 internal constant SYS_MUNMAP = 4091;
uint32 internal constant FD_STDIN = 0;
uint32 internal constant FD_STDOUT = 1;
......
......@@ -435,7 +435,7 @@ contract MIPS2_Test is CommonTest {
MIPS2.ThreadState memory expectThread = copyThread(thread);
expectThread.pc = thread.nextPC;
expectThread.nextPC = thread.nextPC + 4;
expectThread.futexAddr = futexAddr;
expectThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
expectThread.registers[2] = sys.SYS_ERROR_SIGNAL;
expectThread.registers[7] = sys.EAGAIN; // errno
threading.replaceCurrent(expectThread);
......
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